树形dp--换根

Posted 斗奋力努

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树形dp--换根相关的知识,希望对你有一定的参考价值。

换根dp

闲话:换根dp其实常见的还是挺简单的 ,就是太菜了,之前学了两次都忘了,写篇总结留给下次忘了再看。
多锻炼身体,身体zui重要。

换根dp一般分为三个步骤
1、先指定一个根节点
2、一次dfs统计子树内的节点对当前节点的贡献
3、一次dfs统计父亲节点对当前节点的贡献并合并统计最终答案

  1. P3478 [POI2008]STA-Station—入门题
    在这里插入图片描述
    本题是入门题,问的简单明了 ,哪个节点为根节点可以使深度之和最大。
    思路:
    1、先任意以一个节点为根节点跑一遍dfs1,记录需要的信息,本题需要去记录每个节点以节点1为根节点在树中包含自身的子树的节点数和在树中的深度
    2、然后再去跑一遍dfs2,去记录分别以某个节点为根节点的值
    3、遍历答案数组ans,找出深度之和最大的节点
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int to[N*2],nex[N*2],fir[N],idx;
int n,maxn,id;
int dep[N],sz[N],ans[N];

void add(int u,int v){
    to[++idx]=v;
    nex[idx]=fir[u];
    fir[u]=idx;
}

void dfs1(int u,int fa){
    sz[u]=1,dep[u]=dep[fa]+1;
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            dfs1(v,u);
            sz[u]+=sz[v];
        }
    }
}

void dfs2(int u,int fa){
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            ans[v]=ans[u]+n-2*sz[v];
            dfs2(v,u);

        }
    }
}

signed main(){
    scanf("%lld",&n);
    for(int i=1;i<n;i++){
        int u,v; scanf("%lld%lld",&u,&v);
        add(u,v),add(v,u);
    }
    dfs1(1,0);
//    for(int i=1;i<=n;i++) ans[1]+=dep[i];
//    for(int i=1;i<=n;i++) cout<<sz[i]<<" ";   //输出以节点1为根节点,每个节点包含自身的子树中节点个数
//    puts("");
//    for(int i=1;i<=n;i++) cout<<dep[i]<<" ";  //输出以节点1为根节点,每个节点在树中的深度,节点1深度为1
//    puts("");
    dfs2(1,0);
    for(int i=1;i<=n;i++){
        if(ans[i]>maxn){
            maxn=ans[i];
            id=i;
        }
    }
    cout<<id<<endl;
}
  1. P2986 [USACO10MAR]Great Cow Gathering G–入门题+
    在这里插入图片描述
    读完题就应该知道这也是一个树形结构,再想一下会发现这应该也是换根dp的题目。废话,本文肯定都是换根dp题

发现:
这一题不像上一个题一样,上一个题可以理解为边值都是1的树,是一个只有点权的树。而这个题是一颗又有边权又有点权的树~~(不知道叫什么,乱命名)~~

思路:
1、先任意以一个节点为根节点跑一遍dfs1,记录需要的信息,本题需要去记录每个节点以节点1为根节点,子树中每个节点都根节点1的距离,以及每个节点的子树中的奶牛数量
2、因为是以节点1为根节点跑的dfs1,所有开始认为所有奶牛到节点1集合,先初始求出到节点1集合的不方便值。
3、然后再去跑一遍dfs2,去记录分别以其他某个节点为根节点的值。(本题这一步有点前缀和的思想)
4、遍历答案数组dp,找出最小的不方便值的节点

//https://www.luogu.com.cn/problem/P2986  
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const int inf=1e15;
int n,a[N],sum;
int to[N*2],nex[N*2],val[N*2],fir[N],idx;
//数组sz[i]表示以i为根节点的子树中奶牛的数量 ,这里根节点是1开始  有点前缀和的思想
//数组dis[i]表示节点i到节点1的距离,数组dp[i]表示会议地点选择节点i时的代价
int dis[N],sz[N],dp[N],minn=inf;  

void add(int u,int v,int w){
    to[++idx]=v;
    val[idx]=w;
    nex[idx]=fir[u];
    fir[u]=idx;
}

void dfs1(int u,int fa){
    sz[u]=a[u];
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            dis[v]+=dis[u]+val[i];
            dfs1(v,u);
            sz[u]+=sz[v];
        }
    }
}

void dfs2(int u,int fa){
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            dp[v]=dp[u]-sz[v]*val[i]+(sz[1]-sz[v])*val[i];
            dfs2(v,u);
        }
    }
}

signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<n;i++){
        int u,v,w; scanf("%lld%lld%lld",&u,&v,&w);
        add(u,v,w),add(v,u,w);
    }
    dfs1(1,0);
    for(int i=2;i<=n;i++) dp[1]+=dis[i]*a[i];
    dfs2(1,0);
    for(int i=1;i<=n;i++) minn=min(minn,dp[i]);
    cout<<minn<<endl;
}
  1. CF1187E Tree Painting–经典题
    大家别看这个题目对应的cf分有点高就不敢写,估计是刚出来的时候分高,学了之后应该感觉类似入门题
    在这里插入图片描述
    思路:好好学习上面做题解题技巧和思想,就可以想办法去切了这题了
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n;
int to[N*2],nex[N*2],fir[N*2],idx;
int sz[N],dp[N],f[N],maxn;
void add(int u,int v){
    to[++idx]=v;
    nex[idx]=fir[u];
    fir[u]=idx;
}

void dfs1(int u,int fa){
    sz[u]=1;
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            dfs1(v,u);
            sz[u]+=sz[v];
            dp[u]+=dp[v];
        }
    }
    dp[u]+=sz[u];
}

void dfs2(int u,int fa){
    for(int i=fir[u];i;i=nex[i]){
        int v=to[i];
        if(v!=fa){
            f[v]=n+f[u]-2*sz[v];
            dfs2(v,u);
        }
    }
}

signed main(){
    scanf("%lld",&n);
    for(int i=1;i<n;i++){
        int u,v; scanf("%lld%lld",&u,&v);
        add(u,v),add(v,u);
    }
    dfs1(1,0);
    f[1]=dp[1];
    dfs2(1,0);
    for(int i=1;i<=n;i++) maxn=max(maxn,f[i]);
    cout<<maxn<<"\\n";
}

总结
换根dp其实就是跑两遍dfs,注意一下转移方程,剩下的就好写了。还是存在一定套路的,多写几题练练手就会有所体会
题目:
1、CF708C Centroids
2、P6419 [COCI2014-2015#1] Kamp

都挺经典的,可以写写,之后有时间再来更新题目

以上是关于树形dp--换根的主要内容,如果未能解决你的问题,请参考以下文章

[树形dp][换根]Maximum White Subtree

POJ3585:Accumulation Degree(换根树形dp)

ABC222 F - Expensive Expense(树形dp换根)

题解 poj3585 Accumulation Degree (树形dp)(二次扫描和换根法)

树形dp经典换根法——cf1187E

肥宅快乐树 换根+树形DP/dfs