zoukankan      html  css  js  c++  java
  • 题解「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;
    }
    
  • 相关阅读:
    入职外包一个月的感受!(读者投稿)
    作为架构风格的 REST 到底是什么
    PHP基础之查找
    PHP基础之排序
    PHP入门之数组
    PHP入门之函数
    PHP入门之流程控制
    PHP入门之类型与运算符
    DC-1靶机实战和分析
    【基础算法】 状态压缩DP---蒙德里安的梦想
  • 原文地址:https://www.cnblogs.com/tommy0103/p/13832494.html
Copyright © 2011-2022 走看看