zoukankan      html  css  js  c++  java
  • 树上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
  • 相关阅读:
    方法级别的抽象
    同事关系处理6个关键点
    面向对象(类与对象)
    初识面向对象
    Python模块学习
    解决ORA-21561: OID generation failed
    Python模块——PrettyTable 模块
    对于python setup.py install安装的包如何卸载
    git问题--Push rejected: Push to origin/master was rejected
    Scrapyd发布爬虫的工具
  • 原文地址:https://www.cnblogs.com/Anonytt/p/13767656.html
Copyright © 2011-2022 走看看