树上dp_学习笔记

1.AcWing.1073 树的中心(换根dp法):

题意:给你一个树,包含n个结点,和n-1条带权无向边。请你在树中找到一个点,使得该点到树中其他结点的最远距离最近,结果输出所求点到树中其他结点的最远距离。

解法:首先分析题目,其实是要我们把每一个点到其他点的最长距离求出来,再求一个其中最短的就可以了,我们来分析一下每一个点可以再树上怎么走,其实就是向上和向下走。我们用d1[u]表示u结点向下走的最长路径,d2[u]表示u结点向下走的次长路径,p1[u]表示u结点向下走最长路径的儿子节点,up[u]表示u节点向上走的最长路径。

以求得向下走的最长路径d1[u]和次长路径d2[u]。那么问题的关键就在于求向上走的最长路径up[u]了。

这个时候我们把结点v当做当前结点,分析其父亲结点u。如果v是u的p1结点,那么up[v]=max(up[u],d2[u])+edge[i].w;如果v不是u的p1结点,那么up[v]=max(up[u],d1[u])+edge[i].w。另外要注意看自己是不是叶结点(即有没有向下的孩子,如果没有的话d1=d2=0)

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int head[maxn],tot;
struct E{
    int to,next,w;
}edge[maxn<<1];
void add(int u,int v,int w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
bool is_leaf[maxn];
int n,p1[maxn],d1[maxn],d2[maxn],up[maxn];
int dfs_d(int u,int fa){
    d1[u]=d2[u]=-INF;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        int d=dfs_d(v,u)+edge[i].w;
        if(d>=d1[u]){
            d2[u]=d1[u];d1[u]=d;
            p1[u]=v;
        }
        else if(d>d2[u]) d2[u]=d;
    }
    if(d1[u]==-INF){
        d1[u]=d2[u]=0;
        is_leaf[u]=true;
    }
    return d1[u];
}
void dfs_up(int u,int fa){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        if(p1[u]==v){
            up[v]=max(d2[u],up[u])+edge[i].w;
        }
        else up[v]=max(up[u],d1[u])+edge[i].w;
        dfs_up(v,u);
    }
}
int main(){
    scanf("%d",&n);mem(head,-1);
    rep(i,1,n-1){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dfs_d(1,-1);
    dfs_up(1,-1);
    int res=d1[1];
    for(int i=2;i<=n;i++){
        if(is_leaf[i]) res=min(res,up[i]);
        else res=min(res,max(up[i],d1[i]));
    }
    cout<<res<<endl;
}
View Code

2.AcWing.1074 二叉苹果树:

题意:给你一个二叉树,n个点,m个要保留的路径数,然后有n-1条带权无向边。然后需要进行删边,但是最后保留边必须与1联通,问所保留的边权值和最大。

题解:树上有依赖性的背包dp问题。

状态定义:dp[i][j]表示当前结点为i作为root的子树中,保留j条边的最大苹果数量和。

状态转移:dp[node][j]=max(dp[node][j], dp[node][j-k-1]+dp[sub_node][k]+w[sub_node])

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next,w;
}edge[maxn<<1];
void add(int u,int v,int w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,m,dp[110][110];
void dfs(int u,int fa){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].w;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j;j--){
            for(int k=0;k+1<=j;k++){
                dp[u][j]=max(dp[u][j],dp[u][j-k-1]+dp[v][k]+w);
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);mem(head,-1);
    rep(i,1,n-1){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dfs(1,-1);
    printf("%d
",dp[1][m]);
}
View Code

3.AcWing.285 没有上司的舞会

题意:给你n个点,n-1条边(u,v,其中v是u的父亲)。每个点都有自己的权值w[i],每条边最多只能选1个点,问最终选择完的最大权值和是多少

题解:树上dp。

状态定义:dp[i][0]表示以当前结点i作为root的子树中,不选择idp[i][1]表示以当前结点i作为root的子树中,选择i

状态转移:

    

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,w[maxn],fa[maxn],root;
int dp[maxn][2];
void dfs(int x){
    dp[x][1]=w[x];
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dfs(v);
        dp[x][0]+=max(dp[v][0],dp[v][1]);
        dp[x][1]+=dp[v][0];
    }
}
int main(){
    scanf("%d",&n);mem(head,-1);
    rep(i,1,n) scanf("%d",&w[i]);
    rep(i,1,n-1){
        int u,v;scanf("%d%d",&u,&v);
        add(v,u);fa[u]=v;
    }
    root=1;
    while(fa[root]) root=fa[root];
    dfs(root);
    cout<<max(dp[root][0],dp[root][1])<<endl;
}
View Code

4.AcWing.323 战略游戏

题意:给你n个点(编号为0 ~ n-1),每条边至少选1个点,问最少能选几个点?

题解:树上dp

状态定义:dp[i][0]表示以当前结点i作为root的子树中,不选择idp[i][1]表示以当前结点i作为root的子树中,选择i

状态转移:

    

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1500+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,fa[maxn];
int dp[maxn][2];
void dfs(int x){
    dp[x][1]=1;
    dp[x][0]=0;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dfs(v);
        dp[x][0]+=dp[v][1];
        dp[x][1]+=min(dp[v][1],dp[v][0]);
    }
}
int main(){
    while(~scanf("%d",&n)){
        mem(head,-1);mem(fa,0);mem(dp,0);tot=0;
        rep(i,1,n){
            int x,t;scanf("%d:(%d)",&x,&t);
            ++x;
            rep(i,1,t){
                int qwq;scanf("%d",&qwq);
                ++qwq;
                fa[qwq]=x;add(x,qwq);
            }
        }
        int root=1;
        while(fa[root]) root=fa[root];
        dfs(root);
        printf("%d
",min(dp[root][1],dp[root][0]));
    }
}
View Code

5.AcWing 1077皇宫看守

题意:给你n个点,n-1条边,一个点可以看守与其相邻的点。每个点有权值,问最小花费

题解:

  

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1500+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,fa[maxn],w[maxn];
int dp[maxn][3];
void dfs(int x){
    dp[x][2]=w[x];
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dfs(v);
        dp[x][0]+=min(dp[v][1],dp[v][2]);
        dp[x][2]+=min({dp[v][0],dp[v][1],dp[v][2]});
    }
    dp[x][1]=INF;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dp[x][1]=min(dp[x][1],dp[x][0]-min(dp[v][1],dp[v][2])+dp[v][2]);
    }
}
int main(){
    int n;scanf("%d",&n);mem(head,-1);
    rep(i,1,n){
        int id,cost;scanf("%d%d",&id,&cost);
        w[id]=cost;
        int t;scanf("%d",&t);
        while(t--){
            int v;scanf("%d",&v);
            fa[v]=id;add(id,v);
        }
    }
    int root=1;
    while(fa[root]) root=fa[root];
    dfs(root);
    cout<<min(dp[root][1],dp[root][2])<<endl;
}
View Code

6.AcWing 287积蓄程度(换根dp)

题意:给你n个点的树,每条边有流量限制,问以其中一个点最为root的最大流流量。

题解:换根dp的处理方法就是二次扫描,第一次dfs_d扫描处理以每个点作为根,其子树对其造成的影响

第二次扫描运用dp的处理,把父节点减去该子节点的信息,和该子节点作为根其子树的信息联立最后确认该子节点的dp信息。

该题注意特判节点indeg为1的情况。我们先用第一次dfs_d扫描,回溯自下而上得出每个点i的子树对于该点的最大流量贡献d[i]。然后自然而然,dp[root]=d[root],以此为起始,再做一次dfs自上而下,dp[v]=d[v]+min(w,dp[u]-min(d[v],w) ), 当然要考虑父节点如果作为root或者说其indeg=1,那么dp[v]=d[v]+w了.

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=2e5+5;
int tot,head[maxn];
struct E{
    int to,next,w;
}edge[maxn<<1];
void add(int u,int v,int w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int d[maxn],dp[maxn],indeg[maxn],root;
int dfs_d(int x,int fa){
    d[x]=0;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].w;
        if(v==fa) continue;
        d[x]+=min(w,dfs_d(v,x));
    }
    if(indeg[x]!=1) return d[x];
    else return INF;
}
void dfs(int x,int fa){
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].w;
        if(v==fa) continue;
        if(indeg[x]==1&&x==root)dp[v]=d[v]+w;
        else dp[v]=d[v]+min(w,dp[x]-min(d[v],w));
        dfs(v,x);
    }
}
int main(){
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);mem(head,-1);tot=0;
        mem(indeg,0);mem(d,0);mem(dp,0);
        rep(i,1,n-1){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);add(v,u,w);
            ++indeg[u],++indeg[v];
        }
        root=1;
        dfs_d(root,-1);
        dp[root]=d[root];
        dfs(root,-1);
        int ans=0;
        rep(i,1,n) ans=max(ans,dp[i]);
        cout<<ans<<endl;
    }
}
/*
1
4
1 2 5
2 3 2
2 4 2
*/
View Code

7.洛谷P3478【POI2008】 STA-Station(简单换根dp)

题意:给定一个 nn 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。一个结点的深度之定义为该节点到根的简单路径上边的数量。

题解:以1作为root点,第一次dfs扫描,自下而上存子树上每个点到子树的根i的贡献。第二次扫描自上而下更新儿子节点v的dp值。

状态转移方程为:dp[v]=d[v]+(dp[x]-d[v]-siz[v])+(n-siz[v])

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define mem(a,b) memset(a,b,sizeof(a))
#define endl '
'
#define ll long long
using namespace std;
const int maxn=1e6+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,d[maxn],siz[maxn];
void dfs_d(int x,int fa){
    siz[x]=1;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dfs_d(v,x);
        siz[x]+=siz[v];
        d[x]+=d[v]+siz[v];
    }
}
ll dp[maxn];
void dfs(int x,int fa){
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dp[v]=d[v]+(dp[x]-d[v]-siz[v])+(n-siz[v]);
        dfs(v,x);
    }
}
int main(){
    scanf("%d",&n);mem(head,-1);
    rep(i,1,n-1){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs_d(1,-1);
    dp[1]=d[1];
    dfs(1,-1);
    ll ans=0;
    int num;
    rep(i,1,n){
        ans=max(ans,dp[i]);
        if(dp[i]==ans) num=i;
    }
    cout<<num<<endl;
}
View Code

8.洛谷P2014[CTSC1997] 选课(可依赖性树上背包dp)

题意:给你n个点,m组依赖关系,和每个点的权值,问可依赖情况最大和

题解:

  

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,m,s[maxn],dp[305][305];
void dfs(int u,int fa){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=s[v];
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>0;j--){
            for(int k=0;k<j;k++){
                dp[u][j]=max(dp[u][j],dp[u][j-k-1]+dp[v][k]+w);
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);mem(head,-1);
    rep(i,1,n){
        int f,wt;scanf("%d%d",&f,&wt);
        add(f,i);s[i]=wt;
    }
    s[0]=0;
    dfs(0,-1);
    cout<<dp[0][m]<<endl;
}
View Code

9.洛谷P1613【跑路】(Floyd+倍增)

题解:用一个bool数组g[x][y][z]来记录x到y是否存在长度为2^z的路程,若存在,则x到y的时间(距离)为1秒,以此建图,求最短路。若x到t存在长度为2^(z-1)的路径,t到y存在长度为2^(z-1)的路径,那么x到y存在长度为2^z的路径。具体实现见代码及注释。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=55+5;
bool g[maxn][maxn][25];
int d[maxn][maxn];
int n,m;
int main(){
    scanf("%d%d",&n,&m);
    mem(d,INF);
    rep(i,1,n) d[i][i]=0;
    rep(i,1,m){
        int u,v;scanf("%d%d",&u,&v);
        g[u][v][0]=true;
        d[u][v]=1;
    }
    for(int i=1;i<=20;i++){
        for(int u=1;u<=n;u++){
            for(int v=1;v<=n;v++){
                for(int k=1;k<=n;k++){
                    if(g[u][v][i-1]&&g[v][k][i-1]){
                        d[u][k]=1;
                        g[u][k][i]=true;
                    }
                }
            }
        }
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(d[i][j]>d[i][k]+d[k][j]){
                    d[i][j]=d[i][k]+d[k][j];
                }
            }
        }
    }
    cout<<d[1][n]<<endl;
}
View Code

*10.洛谷P3177【HAOI2015树上染色】 (树上背包) 

题解:dp[x][i]表示以x为根的子树中有i个黑色点的情况。那么可想而知,其黑色点的数目的必定范围必定符合[ 0 , siz[x] ]的区间,所以这个时候利用01背包的性质,其儿子节点为v,儿子节点为根的黑色点数量为l,那么状态转移方程为:dp[x][j] = max (dp[x][j],dp[x][j-l] + dp[v][l] +该边的贡献)。然后我看了一下前面的几道树上背包dp,第一维for循环枚举都是从大到小枚举,第二维for循环枚举大小无所谓,但是这道题来说,从小到大就TLE,从大到小就AC,我也不太清楚原理。最后其实无所谓以哪个节点作为root,那么就假定root=1吧,因为不管root为哪个节点,答案最终都是一样的。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=2000+5;
int tot,head[maxn];
struct E{
    int to,next,w;
}edge[maxn<<1];
void add(int u,int v,int w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,k,siz[maxn];
ll dp[maxn][maxn];
void dfs(int x,int fa){
    dp[x][0]=dp[x][1]=0;siz[x]=1;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,x);
        siz[x]+=siz[v];
        for(int j=min(k,siz[x]);j>=0;j--){
            if(dp[x][j]!=-1) dp[x][j]+=dp[v][0]+(ll)siz[v]*(n-k-siz[v])*edge[i].w;
            for(int l=min(j,siz[v]);l>0;l--){
                if(dp[x][j-l]==-1)continue;
                ll val=(ll)(l*(k-l)+(siz[v]-l)*(n-k-siz[v]+l))*edge[i].w;
                dp[x][j]=max(dp[x][j],dp[x][j-l]+dp[v][l]+val);
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&k);mem(head,-1);mem(dp,-1);
    rep(i,1,n-1){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dfs(1,-1);
    cout<<dp[1][k]<<endl;
}
View Code

11.洛谷P1270【“访问”美术馆】 (树上依赖性背包dp)

题解:这个题读入用递归读入(学了一手)。dp[i][j]表示以i为子树的根,花费j时间所能得到的最多画的数量。

那么状态转移方程就是 dp[u][i] = max(dp[u][i] , dp[2*u][j]+dp[2*u+1][i-cost[u]-j] ); 其实说通俗点就是 当父亲结点u的时间固定为i,减去那个路费,然后枚举0~i作为左边,剩下作为右边的情况的最大值

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int t,cost[maxn],val[maxn],dp[1005][2005];
void input(int u){
    int x,y;scanf("%d%d",&x,&y);
    cost[u]=2*x;val[u]=y;
    if(!val[u]){
        input(2*u);
        input(2*u+1);
    }
}
void dfs(int u){
    if(val[u]){
        for(int i=0;i/5<=val[u]&&cost[u]+i<=t;i++){
            dp[u][cost[u]+i]=i/5;
        }
        return ;
    }
    dfs(2*u);
    dfs(2*u+1);
    for(int i=cost[u];i<=t;i++){
        for(int j=0;j+cost[u]<=i;j++){
            dp[u][i]=max(dp[u][i],dp[2*u][j]+dp[2*u+1][i-cost[u]-j]);
        }
    }
}
int main(){
    scanf("%d",&t);t-=1;
    input(1);
    dfs(1);
    cout<<dp[1][t]<<endl;
}
View Code

12.洛谷P1131 【时态同步】(树上一维dp)

题解:从底向根不断更新,对于每个点,找出其子结点最大距离,然后用最大距离减去其他子节点距离。然后你需要从底下往上更新这样,因为更新的边越靠近root,那么花费的的代价就越少,就是一个自下而上的回溯过程。状态转移方程是:dp[x] += dp[v] + (dis[x]-(dis[v]+edge[i].w));

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e6+5;
int tot,head[maxn];
struct E{
    int to,next,w;
}edge[maxn<<1];
void add(int u,int v,int w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,root;ll dp[maxn],dis[maxn];
void dfs(int x,int fa){
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,x);
        dis[x]=max(dis[x],dis[v]+edge[i].w);
    }
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dp[x]+=dp[v]+(dis[x]-(dis[v]+edge[i].w));
    }
}
int main(){
    scanf("%d%d",&n,&root);mem(head,-1);
    rep(i,1,n-1){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dfs(root,-1);
    cout<<dp[root]<<endl;          
}
View Code

13.洛谷P2986 【USACO10MAR Great Cow Gathering G】(简易换根dp+二次扫描dfs)

题解:不知道为什么看到这个题直接脑海里就蹦出换根dp的做法,d[u]表示以u为根的子树中每个点到其的贡献和,up[u]表示u的祖辈们对u的贡献和

然后大概很容易能推出状态转移的 dp方程: up[v]=up[u]+(T-siz[u])*edge[i].w+d[u]-d[v]-siz[v]*edge[i].w+(siz[u]-siz[v])*edge[i].w;

最后就是 ans=min(ans,up[i]+d[i])。注意开long long!

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;ll w;
}edge[maxn<<1];
void add(int u,int v,ll w){
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
bool is_leaf[maxn];
int n,a[maxn],siz[maxn];
ll d[maxn],up[maxn],T;
void init(int u,int fa){
    siz[u]=a[u];
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        init(v,u);
        siz[u]+=siz[v];
    }
}
ll dfs_d(int u,int fa){
    d[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        d[u]+=dfs_d(v,u)+siz[v]*edge[i].w;
    }
    return d[u];
}
void dfs_up(int u,int fa){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        up[v]=up[u]+(T-siz[u])*edge[i].w+d[u]-d[v]-siz[v]*edge[i].w+(siz[u]-siz[v])*edge[i].w;
        dfs_up(v,u);
    }
}
int main(){
    scanf("%d",&n);mem(head,-1);
    rep(i,1,n) scanf("%d",&a[i]);
    rep(i,1,n-1){
        int u,v;ll w;scanf("%d%d%lld",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    init(1,-1);
    T=siz[1];
    dfs_d(1,-1);
    up[1]=0;
    dfs_up(1,-1);
    ll ans=inf;
    rep(i,1,n){
        ans=min(ans,d[i]+up[i]);
    }    
    cout<<ans<<endl;
}
View Code

14.洛谷P2279【HNOI2003】消防局的设立(树上状态机dp)

题解:https://www.luogu.com.cn/blog/rickole/solution-p2279    这个博主写的很好很清楚

大概就是设立5个状态。我发现这种树上状态机dp转移题通常涉及的关系无非是当前结点u和儿子节点v直接的直接关系,通过状态机的不同state之间进行状态的合理转移,我打算写一篇专门针对树上状态机dp转移的一篇总结性的题解。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int dp[maxn][5];
void dfs(int x,int fa){
    dp[x][0]=1;dp[x][3]=0;dp[x][4]=0;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,x);
        dp[x][0]+=dp[v][4];
        dp[x][3]+=dp[v][2];
        dp[x][4]+=dp[v][3];
    }
    if(head[x]==-1){
        dp[x][1]=dp[x][2]=1;
    }
    else{
        dp[x][1]=dp[x][2]=INF;
        for(int i=head[x];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            if(v==fa) continue;
            int F1=dp[v][0];
            int F2=dp[v][1];
            for(int j=head[x];j!=-1;j=edge[j].next){
                int t=edge[j].to;
                if(t==fa||t==v) continue;
                F1+=dp[t][3];
                F2+=dp[t][2];
            }
            dp[x][1]=min(dp[x][1],F1);
            dp[x][2]=min(dp[x][2],F2);
        }
    }
    for(int i=1;i<=4;i++){
        dp[x][i]=min(dp[x][i],dp[x][i-1]);
    }
}
int main(){
    int n;cin>>n;mem(head,-1);
    rep(i,2,n){
        int x;cin>>x;
        add(x,i);
    }
    dfs(1,-1);    
    cout<<dp[1][2]<<endl;
}
View Code

15.洛谷P3047【USACO12FEB】 Nearby Cow G(换根dp二次扫描+容斥原理)

题解:换根dp好想到,第一次dfs也容易知道状态转移。主要是第二次容斥怎么弄,我画了图才明白转移方程是 ans[v][j] += ans[x][j-1]-dp[v][j-2];因为x的j-1部分与原先v的j部分(未更新前ans[v][j] = dp[v][j]),而重叠部分刚好就是dp[v][j-2],于是就把它减去就可以了,这就是容斥的转移

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,k;
int w[maxn],dep[maxn],dp[maxn][25];
void dfs(int x,int f){
    rep(i,0,k) dp[x][i]=w[x];
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f) continue;
        dfs(v,x);
        for(int j=1;j<=k;j++){
            dp[x][j]+=dp[v][j-1];
        }
    }
}
int ans[maxn][25];
void dfs2(int x,int f){
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f) continue;
        ans[v][1]+=dp[x][0];
        for(int j=2;j<=k;j++){
            ans[v][j]+=ans[x][j-1]-dp[v][j-2];
        }
        dfs2(v,x);
    }
}
int main(){
    cin>>n>>k;mem(head,-1);
    rep(i,1,n-1){
        int u,v;cin>>u>>v;
        add(u,v);add(v,u);
    }
    rep(i,1,n) cin>>w[i];
    dfs(1,-1);
    rep(i,1,n){
        rep(j,0,k){
            ans[i][j]=dp[i][j];
        }
    }
    dfs2(1,-1);
    rep(i,1,n){
        cout<<ans[i][k]<<endl;
    }
}
View Code

16.洛谷P4084【USACO17DEC】 Barn Painting G(树上状态机dp)

题解:这个比较简单,dp[i][j]表示当前结点为i颜色为j有多少种情况。那么就很简单了。状态转移方程是:

if(!fg1) dp[u][1]*=((dp[v][3]+dp[v][2])%mod),dp[u][1]%=mod;
if(!fg2) dp[u][2]*=((dp[v][3]+dp[v][1])%mod),dp[u][2]%=mod;
if(!fg3) dp[u][3]*=((dp[v][1]+dp[v][2])%mod),dp[u][3]%=mod;

最后输出(dp[root][1]+dp[root][2]+dp[root][3])%mod,记得开ll和一些特判吧

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '
'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,q,col[maxn];
ll dp[maxn][4];
void dfs(int u,int fa){
    int ct=0;
    int fg1=0,fg2=0,fg3=0;
    dp[u][1]=dp[u][2]=dp[u][3]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        if(col[v]==1) fg1=1,dp[u][1]=0;
        if(col[v]==2) fg2=1,dp[u][2]=0;
        if(col[v]==3) fg3=1,dp[u][3]=0;
    }
    if(fg1&&fg2&&fg3){
        cout<<"0"<<endl;
        exit(0);
    }
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        ++ct;
        dfs(v,u);
        if(!col[u]){
            if(!fg1) dp[u][1]*=((dp[v][3]+dp[v][2])%mod),dp[u][1]%=mod;
            if(!fg2) dp[u][2]*=((dp[v][3]+dp[v][1])%mod),dp[u][2]%=mod;
            if(!fg3) dp[u][3]*=((dp[v][1]+dp[v][2])%mod),dp[u][3]%=mod;
        }else{
            if(col[u]==1){
                dp[u][1]*=((dp[v][3]+dp[v][2])%mod);
                dp[u][1]%=mod;
                dp[u][2]=dp[u][3]=0;
            }
            else if(col[u]==2){
                dp[u][2]*=((dp[v][1]+dp[v][3])%mod);
                dp[u][2]%=mod;
                dp[u][1]=dp[u][3]=0;
            }
            else if(col[u]==3){
                dp[u][3]*=((dp[v][1]+dp[v][2])%mod);
                dp[u][3]%=mod;    
                dp[u][1]=dp[u][2]=0;
            }
        }
    }
    if(ct==0){
        if(!col[u]) dp[u][1]=dp[u][2]=dp[u][3]=1;
        else{
            rep(i,1,3){
                if(i==col[u]) dp[u][i]=1;
                else dp[u][i]=0;
            }
        }
    }
}
int main(){
    cin>>n>>q;mem(head,-1);
    rep(i,1,n-1){
        int u,v;cin>>u>>v;
        add(u,v);add(v,u);
    }
    rep(i,1,q){
        int x,y;cin>>x>>y;
        col[x]=y;
    }
    dfs(1,-1);
    cout<<(dp[1][1]+dp[1][2]+dp[1][3])%mod<<endl;
}
View Code
原文地址:https://www.cnblogs.com/Anonytt/p/13767656.html