[学习笔记]树形dp

本文深入探讨树形动态规划(DP)的概念与应用,通过分析多种典型问题,如晚会人员选择、苹果树修剪等,讲解了树形DP的状态定义、状态转移方程及其实现细节,适合初学者及进阶学习者。

最近几天学了一下树形\(dp\) 其实早就学过了 来提高一下打开树形\(dp\)的姿势。

1、没有上司的晚会

我的人生第一道树形\(dp\),其实就是两种情况:

\(dp[i][1]\)表示第i个人来时的最大人数

\(dp[i][0]\)表示第i个人不来时的最大人数

然后递归至叶子节点,倒推\(dp\)

状态转移方程:

\[dp[root][1]+=dp[G[root][i]][0];\]
\[dp[root][0]+=max(dp[G[root][i]][1],dp[G[root][i]][0]);\]

\(Code\ Below:\)

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
int n;
int f[6010];
int dp[6010][2];
vector<int> G[6010];
//dp[i][1]表示第i个人来
//dp[i][0]表示第i个人不来 

void dfs(int root)
{
    for(int i=0;i<G[root].size();i++){
        dfs(G[root][i]);
    }
    for(int i=0;i<G[root].size();i++){
        dp[root][1]+=dp[G[root][i]][0];
        dp[root][0]+=max(dp[G[root][i]][1],dp[G[root][i]][0]);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&dp[i][1]);
        dp[i][0]=0;f[i]=i;
    }
    int u,v;
    while(1){
        scanf("%d%d",&u,&v);
        if(!u&&!v) break;
        f[u]=v;
        G[v].push_back(u);
    }   
    int root=1;
    while(root!=f[root])
        root=f[root];
    dfs(root);
    printf("%d\n",max(dp[root][1],dp[root][0]));
    return 0;
}

2、二叉苹果树

水题

有三种情况:

1、只剪左儿子

2、只剪右儿子

3、左儿子和右儿子都剪一点

然后枚举每个节点剪几条树枝,就水过了

\(Code\ Below:\)

#include <bits/stdc++.h>
#define maxn 110
using namespace std;
int f[maxn][maxn],vis[maxn];
int T[maxn][maxn],e[maxn][maxn],n,q;

int dfs(int root,int m){
    if(m==0) return 0;
    if(f[root][m]) return f[root][m];
    int ans=0,sum=0;
    if(T[root][0]) sum=dfs(T[root][0],m-1)+e[root][T[root][0]];
    ans=max(ans,sum);
    if(T[root][1]) sum=dfs(T[root][1],m-1)+e[root][T[root][1]];
    ans=max(ans,sum);
    for(int j=1;j<m;j++){
        sum=0;
        if(T[root][0]) sum+=dfs(T[root][0],j-1)+e[root][T[root][0]];
        if(T[root][1]) sum+=dfs(T[root][1],m-j-1)+e[root][T[root][1]];
        ans=max(ans,sum);
    }
    f[root][m]=ans;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&q);
    vis[1]=1;
    for(int i=1;i<n;i++){
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        if(vis[y]) swap(x,y);
        e[x][y]=w;vis[y]=1;
        if(T[x][0]) T[x][1]=y;
        else T[x][0]=y;
    }   
    printf("%d\n",dfs(1,q));
    return 0;
}

3、战略游戏

同没有上司的晚会,就是把\(max\)改成\(min\)

\(Code\ Below:\)

#include <bits/stdc++.h>
#define maxn 1510
using namespace std;
vector<int> T[maxn];
int n,dp[maxn][2],f[maxn];

void dfs(int root){
    for(int i=0;i<T[root].size();i++)
        dfs(T[root][i]);
    for(int i=0;i<T[root].size();i++){
        dp[root][1]+=min(dp[T[root][i]][0],dp[T[root][i]][1]);
        dp[root][0]+=dp[T[root][i]][1];
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        f[i]=i;
        dp[i][1]=1;
        dp[i][0]=0;
    }
    for(int i=0;i<n;i++){
        int m,k;
        scanf("%d%d",&m,&k);
        for(int j=0;j<k;j++){
            int son;
            scanf("%d",&son);
            T[m].push_back(son);
            f[son]=m;
        }
    }
    int root=0;
    while(root!=f[root])
        root=f[root];
    dfs(root);
    printf("%d\n",min(dp[root][0],dp[root][1]));
    return 0;
}

4、“访问”美术馆

蛋疼的输入

输入完就\(dp\),其实可以叫做记搜吧,枚举到每个画廊的所有时间

\(Code\ Below:\)

#include <bits/stdc++.h>
#define maxn 1010
using namespace std;
int dp[maxn][maxn];
struct T{
    int cost,val;
}T[maxn<<2];
int Time;

void Init(int now){
    scanf("%d%d",&T[now].cost,&T[now].val);
    T[now].cost<<=1;
    if(!T[now].val){
        Init(now<<1);Init(now<<1|1);
    }
}

int dfs(int x,int tot){
    if(dp[x][tot]||!tot) return dp[x][tot];
    if(T[x].val) return dp[x][tot]=min(T[x].val,(tot-T[x].cost)/5);
    for(int i=0;i<=tot-T[x].cost;i++)
        dp[x][tot]=max(dp[x][tot],dfs(x<<1,i)+dfs(x<<1|1,tot-T[x].cost-i));
    return dp[x][tot];
}

int main()
{
    scanf("%d",&Time);
    Time--;
    Init(1);
    printf("%d",dfs(1,Time));
    return 0;
}

5、vacation

水题

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=50000+10;
int n,dp[maxn][2];
vector<int> tree[maxn];

void dfs(int x,int fa)
{
    for(int i=0;i<tree[x].size();i++)
        if(tree[x][i]!=fa)
            dfs(tree[x][i],x);
    dp[x][1]=1;
    for(int i=0;i<tree[x].size();i++){
        if(tree[x][i]==fa) continue;
        dp[x][0]+=max(dp[tree[x][i]][0],dp[tree[x][i]][1]);
        dp[x][1]+=dp[tree[x][i]][0];
    }
}

int main()
{
    scanf("%d",&n);
    int x,y;
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        tree[x].push_back(y);
        tree[y].push_back(x);
    }
    dfs(1,0);
    printf("%d\n",max(dp[1][0],dp[1][1]));
    return 0;
}

6、gather

\(hzwer\)的博客讲的比我好

其实就是换根,推理过程已注释

\(Code\ Below:\)

#include <bits/stdc++.h>
#define ll long long
#define INF 1000000000000000
using namespace std;
const ll maxn=100000+10;
ll n,head[maxn],siz[maxn],tot,sum,ans;
/*
ans'=ans+(sum-siz[e[i].to])*e[i].val-siz[e[i].to]*e[i].val
=>ans'=ans+(sum-2*siz[e[i].to])*e[i].val;
(sum-2*siz[e[i].to]<0)ans=>ans'
*/
struct node{
    ll to,next,val;
}e[maxn<<1];

inline void add(ll x,ll y,ll w){
    e[++tot].to=y;
    e[tot].val=w;
    e[tot].next=head[x];
    head[x]=tot;
}

void dfs(ll x,ll fa,ll dis)
{
    ans+=dis*siz[x];
    for(ll i=head[x];i;i=e[i].next){
        ll y=e[i].to;
        if(y==fa) continue;
        dfs(y,x,dis+e[i].val);
        siz[x]+=siz[y];
    }
}

void solve(ll x,ll fa){
    for(ll i=head[x];i;i=e[i].next){
        ll y=e[i].to;
        if(y==fa) continue;
        if(sum-2*siz[y]<0) {
            ans+=(sum-2*siz[y])*e[i].val;
            solve(y,x);
        }
    }
}

int main()
{
    scanf("%lld",&n);
    ll x,y,w;
    for(ll i=1;i<=n;i++){
        scanf("%lld",&siz[i]);
        sum+=siz[i];
    }
    for(ll i=1;i<n;i++){
        scanf("%lld%lld%lld",&x,&y,&w);
        add(x,y,w);add(y,x,w);
    }
    dfs(1,0,0);solve(1,0);
    printf("%lld\n",ans);
    return 0;
}

7、Barn Painting

手动写\(if\)判断,然后统计\(dfs\)一下,记录\(sum\),乘一下

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=100000+10;
const ll p=1e9+7;
ll n,k,head[maxn],to[maxn<<1],nxt[maxn<<1],vis[maxn],tot,ans;ll dp[maxn][3];

inline void add(ll x,ll y){
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}

ll dfs(ll x,ll col,ll fa)
{
    ll acol,bcol;
    if(col==0) acol=1,bcol=2;
    if(col==1) acol=0,bcol=2;
    if(col==2) acol=0,bcol=1;
    if(vis[x]!=-1&&vis[x]!=col) return dp[x][col]=0;
    if(dp[x][col]>=0) return dp[x][col];
    dp[x][col]=1;
    for(ll i=head[x];i;i=nxt[i]){
        ll y=to[i];
        if(y==fa) continue;
        ll sum=0;
        sum=(sum+dfs(y,acol,x))%p;
        sum=(sum+dfs(y,bcol,x))%p;
        dp[x][col]=(dp[x][col]*sum)%p;
    }
    return dp[x][col];
}

int main()
{
    memset(vis,-1,sizeof(vis));
    memset(dp,-1,sizeof(dp));
    scanf("%lld%lld",&n,&k);
    ll x,y;
    for(ll i=1;i<n;i++){
        scanf("%lld%lld",&x,&y);
        add(x,y);add(y,x);
    }
    for(ll i=1;i<=k;i++){
        scanf("%lld%lld",&x,&y);
        vis[x]=--y;
    }
    for(ll i=0;i<3;i++)
        dfs(1,i,1);
    for(ll i=0;i<3;i++)
        ans=(ans+dp[1][i])%p;
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/owencodeisking/p/9528239.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值