树形DP

Posted luowentao

tags:

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


layout: post
title: 树形DP
author: "luowentaoaa"
catalog: true
tags:
mathjax: true
- DP


传送门

A.HDU - 1520 Anniversary party

父亲结点和子结点不能同取

题意

给出一棵树 每个节点有权值 要求父节点和子节点不能同时取 求能够取得的最大值

思路

dp[now]0 表示不取当前结点

dp[now]1 表示取当前结点

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int happy[maxn],dp[maxn][2],father[maxn],n;
vector<int>ve[maxn];
void dfs(int x){
    dp[x][0]=0;
    dp[x][1]=happy[x];
    for(int i=0;i<ve[x].size();i++){
        int v=ve[x][i];
        dfs(v);
        dp[x][0]+=max(dp[v][0],dp[v][1]);
        dp[x][1]+=dp[v][0];
    }
    return ;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n){
    for(int i=1;i<=n;i++){
        cin>>happy[i];
        ve[i].clear();
        dp[i][0]=dp[i][1]=0;
        father[i]=-1;
    }
    while(true){
        int a,b;
        cin>>a>>b;
        if(!a&&!b)break;
        ve[b].push_back(a);
        father[a]=b;
    }
    for(int i=1;i<=n;i++){
        if(father[i]==-1){
            dfs(i);
            cout<<max(dp[i][0],dp[i][1])<<endl;
            break;
        }
    }
    }
    return 0;
}

B.POJ - 1655 Balancing Act

树的重心

题意

求某个点:以这个点为根结点的子树中顶点个数的最大值作为这个点的价值,那么找出价值最小的点,并且输出最小值,价值相等输出靠前的点。

题解

就是求树的重心。

树的重心:的重心也叫的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

dp|now|[1]表示以p为根的子树节点个数(包括本身),dp[p]0]表示以p为balance node的最大子集个数。

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=2e4+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
/*struct node{
    int to;
    int dep;
};*/
vector<int>ve[maxn];
int dp[maxn][2];
void init(int n){
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=n;i++)ve[i].clear();
}
void dfs(int now,int pre,int n){
    dp[now][0]=0;dp[now][1]=1;
    for(int i=0;i<ve[now].size();i++){
        int to=ve[now][i];
        if(to==pre)continue;
        dfs(to,now,n);
        dp[now][1]+=dp[to][1];
        dp[now][0]=max(dp[now][0],dp[to][1]);
    }
    dp[now][0]=max(dp[now][0],n-dp[now][1]);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        init(n);
        for(int i=1;i<n;i++){
            int a,b;
            cin>>a>>b;
            ve[a].push_back(b);
            ve[b].push_back(a);
        }
        dfs(1,-1,n);
        int mi=inf,mid;
        for(int i=1;i<=n;i++){
            if(mi>dp[i][0])mi=dp[i][0],mid=i;
        }
        cout<<mid<<" "<<mi<<endl;
    }
    return 0;
}

C.HDU - 2196 Computer

给出一棵树,求离每个节点最远的点的距离

题解

可以知道 最远距离 肯定要么是当前结点到自己的叶子结点,或者当前结点到另一个其他结点的叶子结点

自己的叶子结点可以dfs递归求出

其他结点的递归结点可以通过自己父亲的最远结点距离得到,但是如果自己就是父亲的最远距离那就只能用次远距离了,所以要同时存储 最远子结点和次远子结点

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e4+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int n;
struct node{
    int to,cap;
};
vector<node>ve[maxn];
int dp[maxn][3];
void init(int n){
    for(int i=0;i<=n;i++)ve[i].clear();
    memset(dp,0,sizeof(dp));
}
void dfs(int now,int pre){
    int mx=0,mx2=0;
    for(int i=0;i<ve[now].size();i++){
        int nex=ve[now][i].to;
        if(nex==pre)continue;
        int ncap=ve[now][i].cap;
        dfs(nex,now);
        if(mx<dp[nex][0]+ncap)mx2=mx,mx=dp[nex][0]+ncap;
        else if(mx2<dp[nex][0]+ncap)mx2=dp[nex][0]+ncap;
    }
    dp[now][0]=mx;
    dp[now][1]=mx2;
}
void dfs1(int now,int pre){
    for(int i=0;i<ve[now].size();i++){
        int nex=ve[now][i].to;
        if(nex==pre)continue;
        int ncap=ve[now][i].cap;
        int mx=0;
        if(ncap+dp[nex][0]==dp[now][0])mx=dp[now][1];
        else mx=dp[now][0];
        dp[nex][2]=max(dp[now][2],mx)+ncap;
        dfs1(nex,now);
    }
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n){
        init(n);
        for(int i=2;i<=n;i++){
            int a,b;
            cin>>a>>b;
            ve[a].push_back(node{i,b});
            ve[i].push_back(node{a,b});
        }
        dfs(1,-1);
        dfs1(1,-1);
        for(int i=1;i<=n;i++){
            cout<<max(dp[i][0],dp[i][2])<<endl;
        }
    }
    return 0;
}

D.UVA - 12186 Another Crisis

为了让信息传到i,需要的最少人数

题意

一个公司有1个老板和n个员工,n个员工中有普通员工和中级员工
现在进行一次投票,若中级员工管理的普通员工中有T%的人投票,则中级员工也投票并递交给上级员工
求最少需要多少个普通员工投票,投票才能到达老板处

题解

存子结点最少的人数,然后排序 再加起来

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
vector<int>ve[maxn];
void add(int a,int b){
    ve[a].push_back(b);
}
int k;
int dfs(int now){
    if(ve[now].size()==0)return 1;
    vector<int>ppp;
    for(int i=0;i<ve[now].size();i++)ppp.push_back(dfs(ve[now][i]));
    int ans=0;
    sort(ppp.begin(),ppp.end());
    int kk=ve[now].size()*k;
    if(kk%100)kk/=100,kk++;
    else kk/=100;
    for(int i=0;i<kk;i++)ans+=ppp[i];
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n>>k){
        if(!n&&!k)break;
        for(int i=0;i<=n;i++)ve[i].clear();
        int a;
        for(int i=1;i<=n;i++)cin>>a,add(a,i);
        cout<<dfs(0)<<endl;
    }
    return 0;
}

E.UVA - 1220 Party at Hali-Bula

父亲结点和子结点不同时取并且情况唯一

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
map<string,int>mp;
int cnt;
int getid(string s){
    if(mp.count(s))return mp[s];
    else mp[s]=cnt++;
    return mp[s];
}
vector<int>ve[250];
int dp[250][2];
bool only[250][2];
void dfs(int now){
    if(ve[now].size()==0){
        dp[now][1]=1;dp[now][0]=0;
        return;
    }
    for(int i=0;i<ve[now].size();i++){
        int nex=ve[now][i];
        dfs(nex);
        dp[now][0]+=max(dp[nex][0],dp[nex][1]);
        if(dp[nex][0]==dp[nex][1])only[now][0]=true;
        else if(dp[nex][0]>dp[nex][1])only[now][0]|=only[nex][0];
        else if(dp[nex][0]<dp[nex][1])only[now][0]|=only[nex][1];
        dp[now][1]+=dp[nex][0];
        only[now][1]|=only[nex][0];
    }
    dp[now][1]++;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    string s,t;
    while(cin>>n&&n){
        cnt=0;
        cin>>s;
        mp.clear();
        memset(dp,0,sizeof(dp));
        memset(only,false,sizeof(only));
        for(int i=0;i<=n;i++)ve[i].clear();
        getid(s);
        for(int i=1;i<n;i++){
            cin>>s>>t;
            ve[getid(t)].push_back(getid(s));
        }
        dfs(0);
        if(dp[0][0]==dp[0][1])cout<<dp[0][0]<<" No"<<endl;
        else if(dp[0][0]>dp[0][1])cout<<dp[0][0]<<((only[0][0])?" No":" Yes")<<endl;
        else cout<<dp[0][1]<<((only[0][1])?" No":" Yes")<<endl;
    }
    return 0;
}

G.CodeForces - 461B Appleman and Tree

每个联通块中只有一个黑色的点

题意

给出一棵树,每个点是白色或者黑色,问有多少种方案能够通过去掉一些边使每个联通块中只有一个黑色的点。

题解

每个联通块只有 有黑点和没有黑点两个选择 设dp{i}[0/1]为以i为根 没有或者有黑点

初始化,如果有黑点设 dp{i}[1]=1,dp{i}[0]=0,否则反过来

设v是i的一个子树,

1.如果i要是黑的

1.1 v是黑的: 那么只能把V的这条边切掉;方案数是i当前为黑的方案数×v当前为黑的方案数

        连接起来     方案数 i当前为白的方案数×v当前为黑的方案数

1.2 v是白的:那就只能i本身是黑才能连接 方案数是i当前为黑的方案数×v当前为白的方案数

2.如果i要是白的

2.1 v是黑的:那么只能选择切掉这条v 方案数是i当前为白的方案数×V当前为黑的方案数

2.2 V是白的: 那就只能选择连接这条V,方案数是i当前白的方案数×v当前为白的方案数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
vector<int>ve[maxn];
ll dp[maxn][2];
int val[maxn];
void add(int a,int b){
    ve[a].push_back(b);
    ve[b].push_back(a);
}
void dfs(int now,int pre){
    if(val[now]==0)dp[now][0]=1,dp[now][1]=0;
    else dp[now][0]=0,dp[now][1]=1;
    for(auto i:ve[now]){
        if(i==pre)continue;
        dfs(i,now);
        ll a=dp[now][0],b=dp[now][1];
        dp[now][0]=(a*dp[i][0]%mod+a*dp[i][1]%mod)%mod;
        dp[now][1]=(a*dp[i][1]%mod+b*dp[i][0]%mod+dp[i][1]*b%mod)%mod;
    }
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<n;i++){
        int x;
        cin>>x;
        add(i,x);
    }
    for(int i=0;i<n;i++)cin>>val[i];
    dfs(0,-1);
    cout<<dp[0][1]<<endl;
    return 0;
}

I.CodeForces - 161D Distance in Tree

长度为K的二元组个数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=5e4+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
vector<int>ve[maxn];
int dp[maxn][550];
void add(int a,int b){
    ve[a].push_back(b);ve[b].push_back(a);
}
ll ans;
int k;
void dfs(int now,int pre){
    dp[now][0]=1;
    for(int i=0;i<ve[now].size();i++){
        int to=ve[now][i];
        if(to==pre)continue;
        dfs(to,now);
        for(int i=0;i<=k;i++)ans+=dp[now][i]*dp[to][k-1-i];
        for(int i=1;i<=k;i++)dp[now][i]+=dp[to][i-1];
    }
   /* for(int i=0;i<=k;i++){
        cout<<"now="<<now<<"	"<<i<<"	dp["<<now<<"]"<<"["<<i<<"]"<<endl;
    }*/
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    cin>>n>>k;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    ans=0;
    dfs(1,-1);
    cout<<ans<<endl;
    return 0;
}

J.CodeForces - 767C Garland

把一个树切成三个权值相同的树

题解

一眼题,但是要注意不仅仅会切成三棵树,有可能切成多个树,所以cnt不能只是==3

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
/*struct node{
    int to;
    int dep;
};*/
vector<int>ve[maxn];
int dp[maxn];
int cnt=0;
int ok[5];
void dfs(int now,int vv){
    for(int i=0;i<ve[now].size();i++){
        int to=ve[now][i];
        dfs(to,vv);
        dp[now]+=dp[to];
    }
    if(dp[now]==vv){
        dp[now]=0;ok[cnt++]=now;
    }
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    cin>>n;
    int root;
    int sum=0;
    for(int i=1;i<=n;i++){
        int a,b;
        cin>>a>>dp[i];
        sum+=dp[i];
        if(!a)root=i;
        else ve[a].push_back(i);
    }
    if(sum%3){cout<<-1<<endl;}
    else{
        dfs(root,sum/3);
        if(cnt>=3)cout<<ok[0]<<" "<<ok[1]<<endl;
        else cout<<-1<<endl;
    }
    return 0;
}

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

Starship Troopers(HDU 1011 树形DP)

HDU1520 Anniversary party(树形dp入门题)

[填坑][支线任务]树形DP 树形背包

hdu1561 树形dp+背包

BZOJ_1060_时态同步_树形DP

HDU 1520 Anniversary party (树形DP)