新的换根dp方法
前言
联考中考了换根dp,发现自己之前理解的换根好拉,特此记录一下高级的换根dp做法。
正文
比如有dp:
- 设 \(f_x\) 为包含以 \(x\) 为根的子树的dp值,\(son_x\) 表示 \(x\) 的子节点集合。
转移:
- 先将 \(son_x\) 按dp值按从小到大排序,即 \(son_{x,1}\) 为子节点中dp值最大的。
- 然后 \(f_x=\max_{i}\{f_{son_{x,i}}+i-1\}\)。
在这种换根dp中,我们需要额外开一个数组 \(g_x\) 表示当根为 \(x\) 时,在以 \(1\) 为根的树中的\(fa_x\) 的dp值是多少。
这样,可以在根为 \(x\) 的时候扫一遍子节点得到,类似与这样:
void sous3(int x,int fa){
扫一遍儿子得到ans[x]
for(int i=h[x];i;i=a[i].ne){
int y=a[i].y;
if(y==fa)continue;
处理 g[y];
}
for(int i=h[x];i;i=a[i].ne){
int y=a[i].y;
if(y==fa)continue;
f[x]=g[y];
sous3(y,x);
}
}
因为处理g[x]
可以用离线的做法(例如前后缀max
处理去掉一个点的整体max
),所以处理灵活,并且也少去了很多信息的记录和撤回。
在此题中,也可以用前后缀的方法,唯一不同的是需要让后缀答案\(-1\)。
void sous3(int x,int fa){
vector<int> b;
//b用来存相邻的节点。
for(int i=h[x];i;i=a[i].ne)b.push_back(a[i].y);
sort(b.begin(),b.end(),paix2);
for(int i=1;i<(int)b.size();++i)
an[x]=max(an[x],f[b[i]]+i-1);
an[x]=max(an[x],(int)b.size()-1)+f[b[0]];
//扫一遍儿子得到ans[x]
nema[b.size()]=0;
for(int i=(int)b.size()-1;i>=0;--i)
nema[i]=max(nema[i+1],f[b[i]]+i);
//先处理后缀max,前缀max直接在求g的时候求
int ma=0;
for(int i=0;i<(int)b.size();++i)
g[b[i]]=max(ma,nema[i+1]-1),ma=max(ma,f[b[i]]+i);
for(int i=0;i<(int)b.size();++i){
if(b[i]==fa)continue;
int y=b[i];
f[x]=max({g[y],1,(int)b.size()-1});
sous3(y,x);
}
}