换根dp的一个trick
最近遇到了很多次这个trick,所以写个博客记录一下。
P2018 消息传递
but \(n\le2e5\)
先考虑根固定的情况,设 \(f_x\) 表示以 \(x\) 为根的子树全部传达需要的时间,一个很显然的思路是先去 \(x\) 的子节点中 \(f\) 值最大的节点,这样能尽可能让每一刻传递信息的节点尽量多。时间复杂度是 \(O(NlogN)\),但是要对每一个点做一次,复杂度是我们不能接受的。
考虑换根dp,我们想要转移答案,就需要先处理出当前节点除去某个子节点后的 \(f\) 值,在删除某一个节点时,在它之前被选的节点不会受到影响,而在它之后被选的节点会整体提前一个单位时间,所以我们记录一个前缀max和后缀max,那删除某一个子节点y后的 \(f\) 值,就是前缀max和后缀max+1中的较大值,到这里换根dp的实现就很明显了。
vector<int> e[N],t[N],pre[N],suf[N];
void dfs(int x,int fa){
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y==fa)continue;
dfs(y,x);t[x].push_back(f[y]);
}
sort(t[x].begin(),t[x].end());
int ans=0;
pre[x].resize(t[x].size());
suf[x].resize(t[x].size());
for(int i=0;i<t[x].size();i++){
if(i==0)pre[x][i]=t[x][i]+t[x].size()-i;
else pre[x][i]=max(t[x][i]+(int)t[x].size()-i,pre[x][i-1]);
ans=max(ans,t[x][i]+((int)t[x].size()-i));
}f[x]=ans;
for(int i=t[x].size()-1;i>=0;i--){
if(i==t[x].size()-1)suf[x][i]=t[x][i]+t[x].size()-i;
else suf[x][i]=max(suf[x][i+1],t[x][i]+(int)t[x].size()-i);
}
}
void exc(int x,int fa){
int ans=0;
sort(t[x].begin(),t[x].end());
pre[x].resize(t[x].size());
suf[x].resize(t[x].size());
for(int i=0;i<t[x].size();i++){
if(i==0)pre[x][i]=t[x][i]+t[x].size()-i;
else pre[x][i]=max(t[x][i]+(int)t[x].size()-i,pre[x][i-1]);
ans=max(ans,t[x][i]+((int)t[x].size()-i));
}g[x]=ans;
for(int i=t[x].size()-1;i>=0;i--){
if(i==t[x].size()-1)suf[x][i]=t[x][i]+t[x].size()-i;
else suf[x][i]=max(suf[x][i+1],t[x][i]+(int)t[x].size()-i);
}
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y==fa)continue;
int p=lower_bound(t[x].begin(),t[x].end(),f[y])-t[x].begin();
int num=0;
if(p>0)num=max(num,pre[x][p-1]-1);
if(p<t[x].size()-1)num=max(num,suf[x][p+1]);
g[y]=max(f[y],num+1);
t[y].push_back(num);
exc(y,x);
}
}
在换根dp时,这种记录前缀后缀来消除某个节点的trick是很常用的

浙公网安备 33010602011771号