树形DP刷题

1.建立虚点:https://www.acwing.com/problem/content/288/

考虑再建立一点作为森林中每一个树的根的根,接下来就是经典的树上背包问题:

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[100010];
int h[100010],e[100010],ne[100010],idx;
int f[1001][1001];
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u){
    for(int i=h[u];i!=-1;i=ne[i]){
        dfs(e[i]);
        for(int j=m-1;j;j--){
            for(int k=1;k<=j;k++) f[u][j]=max(f[u][j],f[u][j-k]+f[e[i]][k]);
        }
    }
    for(int i=m;i;i--) f[u][i]=f[u][i-1]+w[u];
}
int main(){
    cin>>n>>m;
    memset(h, -1, sizeof h);
    for(int i=1;i<=n;i++){
        int p;
        cin>>p>>w[i];
        add(p,i);
    }
    m++;
    dfs(0);
    cout<<f[0][m];
} 

2.二次扫描,换根DP:https://www.acwing.com/problem/content/289/

首先,我们不妨以1为根节点,令dp[i]表示以i为源点,从其流向子树的最大流量。

对于度为1(除根节点外)的赋为正无穷,另外的易得状态转移方程:

dp[x]=\sum min(c(x,y),dp[y]),其中yx的子节点。

现在我们考虑换根(也就是把用父亲状态跟新儿子):

对于根节点y的一个儿子x来说,我们记f[x]表示它流向整个水系的最大流量。

我们考虑比较普通的情况:它由两部分组成:

1.它自己子树的流量  2.流过它父亲的最大流量。

易得:f[x]=dp[x]+min(c(x,y),f[y]-min(dp[x],c(x,y)))

不过我们还需考虑几个特殊情况:

1.x的度为1,那么它的流量就直接是流过他父亲的最大流量:即上述右部分。

2.它的父亲y度为1,那么它流向父亲的最大流量就是c(x,y)

事实上那个普通情况就是deg(x),deg(y)!=1的情况。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int t;
int x,y,z;
int w[200010],d[200010],f[200010];
int n;
struct node{
    int dian,zhi;
};
vector<node> edge[200010];
int dg[201000];
void dfs1(int root,int fa){
    if(dg[root]==1&&root!=1){
        d[root]=1e9;
        return;
    }
    d[root]=0;
    for(int i=0;i<edge[root].size();i++){
        int x=edge[root][i].dian;
        if(x==fa) continue;
        dfs1(x,root);
        d[root]+=min(d[x],edge[root][i].zhi);
    }
}
void dfs2(int root,int fa){
    for(int i=0;i<edge[root].size();i++){
        int x=edge[root][i].dian;
        if(x==fa) continue;
        if(dg[x]==1){
            f[x]=f[root]-edge[root][i].zhi;
        }
        else if(dg[root]==1){
            f[x]=d[x]+edge[root][i].zhi;
            dfs2(x,root);
        }
        else{
            f[x]=d[x]+min(edge[root][i].zhi,f[root]-min(edge[root][i].zhi,d[x]));
            dfs2(x,root);
        }
        
    }
}
int main(){
    cin>>t;
    while(t--){
        memset(edge,0,sizeof(edge));
        memset(dg, 0, sizeof(dg));
        cin>>n;
        for(int i=1;i<=n-1;i++){
            cin>>x>>y>>z;
            dg[x]++,dg[y]++;
            edge[x].push_back({y,z});
            edge[y].push_back({x,z});
        }
        dfs1(1,-1);
        f[1]=d[1];
        dfs2(1,-1);
        int res=0;
        for(int i=1;i<=n;i++) res=max(res,f[i]);
        cout<<res<<endl;;
    }
}

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值