题解:CF2254G Nightcrawler
似乎爆标了?
从
对于两个集合
那么现在问题变为,每次可以选择一对存在祖先关系的
什么样的
::::info[AI 提供的严格证明]
如何选择与 u 合并的点?
先把已经删除的点忽略。对于每个剩余点,把它最近的、仍未被删除的祖先视为父亲,这样剩余点就构成了一片森林。
注意,这只是为了方便理解,实际实现时不需要真的维护这片森林。
情况一:u 还有后代
这时,与任意一个仍存在的后代
因为我们选择的是权值最小的可合并点
也就是说,无论选择哪个后代,操作后的状态都完全一样,因此不需要纠结应该选择哪个后代。
情况二:u 没有后代,但还有祖先
这时,选择
合并之后,删除
这里不能随便选择一个祖先,必须保证我们下面证明所用的“最近祖先”性质。
情况三:u 既没有祖先,也没有后代
这说明
而且,后续操作只会删除点、修改权值,不会让两个原本没有祖先关系的点突然产生祖先关系。因此,
注意,这里只是不再考虑它,并不是把它对应的集合删掉:它的权值仍然要计入答案。
因此,我们每次实际选择的,是所有还能参与合并的点中,权值最小的那个点。
为什么这样贪心是对的?
我们证明一个比“这一步最优”更强的结论:
假设当前还有
m 个集合。按照上述规则合并一次后,对于任意k<m ,都不会失去得到最优k 集合划分的机会。
换句话说,这一步合并不仅保住了
为了方便表述,下面把满足祖先关系的集合称为一条“链”。这里的链不要求包含路径上的所有点。
一个小结论:拆开一条链,能增加多少分?
设
对于任意一条包含至少两个点的链,保留一个最大值在原链中,再把另一个点
这样,原链的得分不变,又多出了一条得分为
由于
因此:
拆开一条多点链,可以让链数增加
1 ,并且让总分至少增加w(u) 。
这个结论可以用来补偿删除一条权值为
证明一:u 有后代时,可以直接删除 u
固定任意一个
分两种情况。
1. u 所在的链不止一个点
直接把
因为
链数不变,总分也不变。
2. u 单独成链
删除这条单点链之后:
- 链数减少
1 ; - 总分减少
w(u) 。
但当前有
将那条链拆开,就能把链数补回来,并且至少补回
所以,删除
反过来,对于删除
- 这条链仍然合法;
- 因为
w(u)\le w(v) ,得分不变。
于是,删除
而且,这里的
证明二:u 没有后代时,可以与最近祖先合并
设
我们要证明:
对于任意
k<m ,都存在一个最优k 链划分,使得u,v 位于同一条链中。
这样就可以放心地先合并
这里有一个非常重要的性质:
在不包含
v 的情况下,任何与u 同链的其他点,都一定在v 上方。
这是因为:
现在取一个最优
否则,设:
下面把这两条链稍作调整,让
1. v 不是链 D 的最大值,即 w(v)<M_D
直接把
- 从
D 中拿走的不是最大值,所以D 的得分不变。 -
- 给
C 加入一个点,得分当然不会下降。
这样就让
2. v 是链 D 的最大值,即 w(v)=M_D
如果
也就是让
先看合法性。
所以,这些剩余点放到一起,仍然是一条合法的链。
再看得分。
新链
而新链
- 如果
C 不止一个点,删掉最小的u ,不会丢掉C 的最大值; - 如果
C=\{u\} ,那么D' 非空,其中任意一个点的权值都至少为w(u)=M_C 。
因此,新得分至少为。
不小于原得分。
3. C=\{u\} ,D=\{v\}
此时直接合并这两条单点链,会让链数减少
但由于
所以,这种情况也能调整成一个不劣的划分,使
综上,总能找到一个最优划分,让
从局部操作得到所有答案
现在,最开始的猜测就得到了证明。
初始时,每个点单独成集,所以。
每次选择权值最小的可合并点
由于每次合并都不会影响所有更小集合数的最优答案,所以,当集合数降到
| 如果已经不存在能够合并的点对,那么当前所有代表点之间都没有祖先关系,无法继续减少集合数,剩余答案全部为 |
|---|
现在我们需要支持删除一个点
最后我们发现每次取出
这份代码加上快读后可以获得 CF 上的所有语言最优解。
#include<bits/stdc++.h>
using namespace std;using ll=long long;
const int N=200005;int a[N],p[N],pos[N],id[N],tmp[N],c[1024];ll ans[N];
struct DSU{
int f[N],top[N];
void init(int n){fill(f,f+n+1,-1),iota(top,top+n+1,0);}
int find(int x){for(;f[x]>=0;x=f[x])if(f[f[x]]>=0)f[x]=f[f[x]];return x;}
void del(int x,int y){y=find(y);int t=top[y];if(f[x]>f[y])swap(x,y);f[x]+=f[y],f[y]=x,top[x]=t;}
}d;
void solve(){
int n,mx=0;cin>>n;ll sum=0;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i],mx=max(mx,a[i]),pos[i]=id[i-1]=i;
for(int i=2;i<=n;i++)cin>>p[i],pos[p[i]]=0;
int *q=id,*r=tmp;
if(n<=64)sort(q,q+n,[](int x,int y){return a[x]<a[y];});
else for(int s=0;s<30&&(mx>>s);s+=10){
fill(c,c+1024,0);
for(int i=0;i<n;i++)c[(a[q[i]]>>s)&1023]++;
for(int i=1;i<1024;i++)c[i]+=c[i-1];
for(int i=n-1;i>=0;i--)r[--c[(a[q[i]]>>s)&1023]]=q[i];
swap(q,r);
}
d.init(n);int k=n;ans[k]=sum;
for(int j=0;j<n;j++){
int i=q[j],u=pos[i],x=d.find(u?p[u]:i),v=d.top[x];
if(!v)continue;if(u)pos[v]=u;
d.del(x,p[v]),sum-=a[i],ans[--k]=sum;
}
for(int i=1;i<=n;i++)cout<<(i<k?-1:ans[i])<<(i==n?'\n':' ');
}int main(){int T;for(cin>>T;T--;)solve();return 0;}