标签:include algo 时间复杂度 ons code math its 最大的 rev
性质题好评。
对于这类题目,我们肯定要先计算出题目中给出的 \(f(x)=\max\limits_{i=1}^ndist(x,i)\)。这个东西可以通过换根 \(\text{DP}\) 在 \(O(n)\) 的时间内计算出来。
选出 \(f_x\) 最小的,以它为根,记作 \(u\)。这棵树的父亲的 \(f\) 值都不大于儿子的 \(f\) 值。因为这样的 \(f_x\) 一定在树的中心上,那么易证。
现在我们有了一个小根堆的结构。题目要求最大的连通块 \(S\) 令 \(\max_{x\in S} f_x-\min_{x\in S}f_x\leq l\)。自然想到枚举每棵子树在以 \(u\) 为根意义下的根,再从这个连通块中取尽可能多符合条件的点。因为子树的根权值不大于子树内权值,所以上式的 \(\min\) 是确定的,我们只需要考虑 \(\max\) 的取值。时间复杂度为 \(O(n^2)\),不能通过。
可以用类似于 [十二省联考2019]春节十二响 的做法来优化上述过程,简略来说就是维护一些堆,合并,删除堆顶,询问堆的 \(size\)。但这里不过多展开,着重介绍一种 \(\text{two-pointer}\) 做法,时间复杂度优于上述做法。
我们将所有点按照 \(f\) 从大到小排序,那么对于 \(\max f_x\) 而言,上界是单调递减的。对于当前点,不断加入贡献。对于不符合条件的点,减去其贡献,这是双指针的精髓。
对于此题而言,我们对于当前枚举的点 \(x\),将所有和它连通的点归入一个并查集。对于不符合条件的点,我们将它所在集合的 \(size\) 减 \(1\)。
每次询问一棵子树能够取的最大连通块时,我们就将其所在并查集的 \(size\) 作为询问的答案。虽然并查集不支持删除操作,但由于我们只会删除当前的叶子节点,所以可以只对其所在并查集的 \(size\) 减 \(1\)。
预处理与排序的时间复杂度为 \(O(n+n\log n)\),回答每组询问的时间复杂度为 \(O(n\alpha(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\n",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\n",ans);
}
return 0;
}
题解「CF516D Drazil and Morning Exercise」
标签:include algo 时间复杂度 ons code math its 最大的 rev
原文地址:https://www.cnblogs.com/tommy0103/p/13832494.html