题解「CF516D Drazil and Morning Exercise」

性质题好评。

对于这类题目,我们肯定要先计算出题目中给出的 (f(x)=maxlimits_{i=1}^ndist(x,i))。这个东西可以通过换根 ( ext{DP})(O(n)) 的时间内计算出来。

选出 (f_x) 最小的,以它为根,记作 (u)。这棵树的父亲的 (f) 值都不大于儿子的 (f) 值。因为这样的 (f_x) 一定在树的中心上,那么易证。

现在我们有了一个小根堆的结构。题目要求最大的连通块 (S)(max_{xin S} f_x-min_{xin S}f_xleq l)。自然想到枚举每棵子树在以 (u) 为根意义下的根,再从这个连通块中取尽可能多符合条件的点。因为子树的根权值不大于子树内权值,所以上式的 (min) 是确定的,我们只需要考虑 (max) 的取值。时间复杂度为 (O(n^2)),不能通过。

可以用类似于 [十二省联考2019]春节十二响 的做法来优化上述过程,简略来说就是维护一些堆,合并,删除堆顶,询问堆的 (size)。但这里不过多展开,着重介绍一种 ( ext{two-pointer}) 做法,时间复杂度优于上述做法。

我们将所有点按照 (f) 从大到小排序,那么对于 (max f_x) 而言,上界是单调递减的。对于当前点,不断加入贡献。对于不符合条件的点,减去其贡献,这是双指针的精髓。

对于此题而言,我们对于当前枚举的点 (x),将所有和它连通的点归入一个并查集。对于不符合条件的点,我们将它所在集合的 (size)(1)

每次询问一棵子树能够取的最大连通块时,我们就将其所在并查集的 (size) 作为询问的答案。虽然并查集不支持删除操作,但由于我们只会删除当前的叶子节点,所以可以只对其所在并查集的 (size)(1)

预处理与排序的时间复杂度为 (O(n+nlog n)),回答每组询问的时间复杂度为 (O(nalpha(n)))

代码中还是有一些细节需要注意(

Show the Code

#include<cstdio>
#include<algorithm>
#define int ll
typedef long long ll;
int cnt=0;
ll mx1[100005],mx2[100005],mx3[100005],f[100005];
int id[100005],rev[100005],size[100005],fa[100005];
int h[100005],son[100005],to[200005],ver[200005],w[200005];
inline int read() {
	register int x=0,f=1;register char s=getchar();
	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
	return x*f;
}
inline int max(const int &x,const int &y) {return x>y? x:y;}
inline int find(int x) {return x==fa[x]? x:fa[x]=find(fa[x]);}
inline void merge(int x,int y) {int fx=find(x),fy=find(y);if(fx!=fy) {fa[fy]=fx;size[fx]+=size[fy];}} 
inline bool cmp(int x,int y) {return f[x]>f[y];} 
inline void add(int x,int y,int z) {
	to[++cnt]=y;
	w[cnt]=z;
	ver[cnt]=h[x];
	h[x]=cnt;
}
inline void dfs1(int x,int fa) {
	for(register int i=h[x];i;i=ver[i]) {
		int y=to[i];
		if(y==fa) continue; dfs1(y,x);
		if(mx1[x]<=mx1[y]+w[i]) {
			mx2[x]=mx1[x]; son[x]=y;
			mx1[x]=mx1[y]+w[i];
		}
		else if(mx2[x]<mx1[y]+w[i]) {mx2[x]=mx1[y]+w[i];}
	}
}
inline void dfs2(int x,int fa) {
	f[x]=max(max(mx1[x],mx2[x]),mx3[x]);
	for(register int i=h[x];i;i=ver[i]) {
		int y=to[i];
		if(y==fa) continue;
		if(son[x]==y) {mx3[y]=mx2[x]+w[i];}
		else {mx3[y]=mx1[x]+w[i];}
		mx3[y]=max(mx3[y],mx3[x]+w[i]); dfs2(y,x);
	}
}
signed main() {
	int n=read();
	for(register int i=1;i<n;++i) {int x=read(),y=read(),z=read();merge(x,y);add(x,y,z);add(y,x,z);}
	dfs1(1,-1); dfs2(1,-1); //for(register int i=1;i<=n;++i) printf("%d
",f[i]);
	for(register int i=1;i<=n;++i) id[i]=i;
	std::sort(id+1,id+1+n,cmp);
	for(register int i=1;i<=n;++i) rev[id[i]]=i;
	int Q=read();
	while(Q--) {
		int lim=read(),cur=1,ans=0;
		for(register int i=1;i<=n;++i) fa[i]=i,size[i]=1;
		for(register int i=1;i<=n;++i) {
			int x=id[i];
			while(cur<=n&&f[id[cur]]>f[x]+lim) {--size[find(id[cur])];++cur;} 
			for(register int j=h[x];j;j=ver[j]) {int y=to[j]; if(rev[y]>i) continue; merge(x,y);}
			ans=max(ans,size[find(x)]);
		}
		printf("%d
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/tommy0103/p/13832494.html