请不要问我为什么不先讲树形dp和树上背包,问就是不知道QAQ
正片
树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。
通常需要两次 DFS,第一次 DFS 预处理诸如深度,点权和之类的信息,在第二次 DFS 开始运行换根动态规划。
——以上内容来自OI WIKI
怎么说呢,换根dp就是把一个不是树根的点提上去,让TA成为树根。这样的话,新的树根的原来的父亲就成了TA现在的儿子,而TA原来的儿子不变。听起来有亿点点绕,那么就来看一道题吧。
----------------------------------------------------不那么华丽的分割线--------------------------------------------------
洛谷P3478
其实我们想一下,每次换一个结点当做根,那么原来这个结点的孩子的深度全部减少1,而TA的"兄弟姐妹"们的深度都全部加1。总结一下,我们让表示i的孩子的个数,表示i为根时的结果,可以得出(x是y原来的父亲)。这里大家可以自行推一下。
OK,那接下来就是代码部分了。(我用了链式前向星存图,如果你们喜欢用二维数组也不是不行)
#include
using namespace std;
struct edge{
int to;
int nxt;
}e[maxn<<1];
int head[maxn];
int cnt,n;
long long ans;
long long res[maxn],dep[maxn],size[maxn];
void add(int u,int v){
e[++cnt].nxt=head[u];
head[u]=cnt;
e[cnt].to=v;
}
void dfs1(int x,int fa){
size[x]=1;
dep[x]=dep[fa]+1;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa)
continue;
dfs1(y,x);
size[x]+=size[y];
}
}
void dfs2(int x,int fa){
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa)
continue;
res[y]=res[x]+n-2*size[y];
dfs2(y,x);
}
}
int main(){
cin>>n;
for(int i=1;i>u>>v;
add(u,v);
add(v,u);
}
dfs1(1,0);
for(int i=1;i<=n;i++)
res[1]+=dep[i];
dfs2(1,0);
int idx=0;
for(int i=1;i<=n;i++){
if(ans
这不再找几题练练?
CF1324F
嗯,需要翻译吗?
直接推公式,令为根是v时的答案。我们做一遍dfs,得出。接下来,开始换根。我们把根v换成根to:①把to的孩子从v当中除掉,②把v变成to的孩子。
OK,然后就没啥问题了,时间复杂度为。贴个代码:
#include
using namespace std;
vector a,ans,dp;
vector> tree;
void dfs(int v,int p=-1){
dp[v]=a[v];
for(int u:tree[v]){
if(u==p)
continue;
dfs(u,v);
dp[v]+=max(dp[u],0);
}
}
void solve(int v,int p=-1){
ans[v]=dp[v];
for(int u:tree[v]){
if(u==p)
continue;
dp[v]-=max(0,dp[u]);
dp[u]+=max(0,dp[v]);
solve(u,v);
dp[u]-=max(0,dp[v]);
dp[v]+=max(0,dp[u]);
}
}
int main(){
int n;
cin>>n;
for(int i=0;i>a[i];
if(a[i]==0)
a[i]=-1;
}
for(int i=0;i>u>>v;
u--;
v--;
tree[u].push_back(v);
tree[v].push_back(u);
}
dfs(0);
solve(0);
for(int i=0;i
最后一题~
我们应该锻炼一下自己读英文题的水平,所以——诶不是,你怎么打开洛谷了?好吧,我知道我是拦不住你的。
相信大家都已近康过翻译了,所以我就不翻了。不要给自己找理由,不会就是不会!
很不显然,这题比上题难(官方给的2100,上题官方评分1800)。那么怎么做呢?我们令为v是根的答案,则。(是v的孩子) 但问题是,我们怎么才能把根从1换到n而且不超时呢?Good Question! 当我们把根从v换成to的时候,我们要把to的那坨子树给砍掉。所以,我们要用去减和,同时改变,让TA减掉to的子树的大小。OK,到这儿,就成功一半了。接下来就是把v变成to的孩子。我们让加上,然后把增加和。也许有点晕,贴上这个片段的代码:
void dfs(int v,int p=-1){
ans=max(ans,dp[v]);
for(auto u:tree[v]){
if(u==p)
continue;
dp[v]-=dp[u];
dp[v]-=sz[u];
sz[v]-=sz[u];
sz[u]+=sz[v];
dp[u]+=sz[v];
dp[u]+=dp[v];
dfs(u,v);
//回溯
dp[u]-=dp[v];
dp[u]-=sz[v];
sz[u]-=sz[v];
sz[v]+=sz[u];
dp[v]+=sz[u];
dp[v]+=dp[u];
}
}
所以,我们先用一趟dfs算出所有的,然后用一趟dfs算出一个点的dp值,最后换根就OK辣。贴上代码:
#include
using namespace std;
long long ans;
vector sz;
vector dp;
vector> tree;
int getsize(int v,int p=-1){
sz[v]=1;
for(auto u:tree[v]){
if(u==p)
continue;
sz[v]+=getsize(u,v);
}
return sz[v];
}
long long precalc(int v,int p=-1){
dp[v]=sz[v];
for(auto u:tree[v]){
if(u==p)
continue;
dp[v]+=precalc(u,v);
}
return dp[v];
}
void dfs(int v,int p=-1){
ans=max(ans,dp[v]);
for(auto u:tree[v]){
if(u==p)
continue;
dp[v]-=dp[u];
dp[v]-=sz[u];
sz[v]-=sz[u];
sz[u]+=sz[v];
dp[u]+=sz[v];
dp[u]+=dp[v];
dfs(u,v);
dp[u]-=dp[v];
dp[u]-=sz[v];
sz[u]-=sz[v];
sz[v]+=sz[u];
dp[v]+=sz[u];
dp[v]+=dp[u];
}
}
int main(){
int n;
cin>>n;
for(int i=0;i>u>>v;
tree[u].push_back(v);
tree[v].push_back(u);
}
sz=vector(n);
dp=vector(n);
getsize(0);
precalc(0);
dfs(0);
cout<
当然,这题好像还可以不换根。这里就不说了(其实是我不会,况且今天换根dp是主角),有兴趣的可以自行研究。
那么本期博客就到这里了。我们下期再见!
注意事项:本期的代码直接提交均无法AC,请各位不要无脑Ctrl C+Ctrl V,看懂之后自己写一遍。