树形DP例题整理

与普通dp类似,但是状态遍历不能简单的++i,而应该按由儿子到父亲的顺序完成。可以通过DFS递归完成,也可以按DFS(BFS)序从大到小完成,以保证先完成儿子再完成父亲。

例题一 皇宫看守 tyvj_3384/vijos_1144
每个节点有三个状态

  • f[i][0]代表i子树全都被染色的代价,由每个儿子的min(0,2)转移而来,且有一个儿子必须为2;
  • f[i][1]代表i子树(不包括i)全部被染色的代价,由每个儿子的min(0,2)转移而来;
  • f[i][2]代表i子树全部被染色,且i处有守卫 的代价,由每个儿子的min(0,1,2)+cost[i]转移而来。

上代码

//tyvj  3384 皇宫看守     vijos1144
//by     cyd
#include<cstdio>
#include<cstring>
#include<iostream>

#include<queue>
#define N 1510
#define min(x,b) (x<b?x:b)   //要加括号的 
#define min2(a,b,c) min(min(a,b),c)
using namespace std;
struct branch{
    int sn,ne;
}t[N];  //邻接表 
int n,head[N],w[N],tmp,tmp2,cnt,bfn[N],num[N];
long long f[N][3];
void addson(int fa,int son){
    cnt++;
    bfn[son]=1;
    t[cnt].sn=son;
    t[cnt].ne=head[fa];
    head[fa]=cnt;
    return ; 
}
void bfs(){  //求bfs序  bfn先用来判断哪个点是根节点  之后bfn[i]=j表示_bfs序为i的节点是j 
    cnt=0;
    queue<int >q;
    for(int i=1;i<=n;++i){
        if(!bfn[i]){
            q.push(i);
            break;
        }
    }
    memset(bfn,0,sizeof bfn);

    while(!q.empty()){
        int u=q.front();
        q.pop();
        bfn[++cnt]=u;
        for(int i=head[u];i;i=t[i].ne){
            q.push(t[i].sn);
        }
    }
    return ;
}
int main(){
    cin>>n;

    for(int i=1;i<=n;++i){
        scanf("%d",&tmp);

        scanf("%d",&w[tmp]);
        scanf("%d",&tmp2);
        num[tmp]=tmp2;
        while(tmp2--){
            int a;
            scanf("%d",&a);
            addson(tmp,a);
        }
    } 
    bfs();
    for(int i=n;i;--i){  //按bfs序从大到小 保证儿子在父亲之前被访问 

        int u=bfn[i];
        long long minn=2100000000,flag=0;
        for(int j=head[u];j;j=t[j].ne){
            int v=t[j].sn;
            f[u][2]+=min2(f[v][0],f[v][1],f[v][2]);
            f[u][1]+=min(f[v][0],f[v][2]);
            f[u][0]+=min(f[v][0],f[v][2]);

            if(f[v][2]<=f[v][0])flag=1;
            else minn=min(minn,f[v][2]-f[v][0]);   //这里很重要 因为状态0有一个儿子必须为2,我们先使所有儿子状态为0,找出“状态0与状态2代价差最小的儿子”,  加上那个差
        }
        f[u][2]+=w[u];
        if(!flag)  
        f[u][0]+=minn;
        if(!num[u])f[u][2]=f[u][0]=w[u];  //如果这个节点是叶子节点 他的0和2代价都为w[u]
    }
    cout<< min(f[bfn[1]][0],f[bfn[1]][2]);  
}

下一题!


IOI 2005 河流 (bzoj 1812)
先附上我当时看的题解
题解
f[i][j][k]表示当前为i节点 ,i的下游(不包括i)里i最近的伐木场是j,当前子树有k个伐木场。
感觉真的很玄学,不知道神犇们怎么想出来的。
直接上代码吧

//ioi 2005 河流 
//bzoj 1812
//by cyd
#include<cstdio>
#include<iostream>
#include<cstring>
#define min(a,b) (a<b?a:b)
using namespace std;
struct tree{
    int l,r,fa;
}t[110];
int n,k,w[110],v[110],d[110],cnt=-1,dfn[110],dis[110];
int f[110][110][60];
void dfs(int k,int now){
    dfn[++cnt]=k;
    now+=d[k];
    dis[k]=now;
    for(int i=t[k].l;i;i=t[i].r){
        dfs(i,now); 
    }
    return ;
}
int main(){
    cin>>n>>k;

    for(int i=1;i<=n;++i){
        scanf("%d%d%d",&w[i],&v[i],&d[i]);
        t[i].r=t[v[i]].l;
        t[v[i]].l=i;
        t[i].fa=v[i];         //将多叉树转化为二叉树方便遍历第三维  左孩子右兄弟 
    }
    dfs(0,0);
    memset(f,0x7f,sizeof f);
    memset(f[0],0,sizeof f[0]);  //这个0其实不是根节点  是叶子节点的下一层 也会访问到0 
    t[0].fa=-1;  //作为终止点 
    for(int i=n;i;--i){
        int p=dfn[i];
        //cout<<p<<endl;
        for(int j=t[p].fa;j!=-1;j=t[j].fa){  //第二维 下游离i最近(不包括i)的伐木场位置
            for(int k1=0;k1<=k;++k1){  //暴力i子树伐木场数量 
                //如果p点是伐木场 
                for(int l=0;l<k1;++l){
                    if(f[  t[p].l ][p][l]!=0x7f7f7f7f&&f[t[p].r][j][k1-l-1]!=0x7f7f7f7f)
                    f[p][j][k1]=min(f[p][j][k1],f[  t[p].l ][p][l]+f[t[p].r][j][k1-l-1] );  

                }
                //如果p点不是伐木场 
                for(int l=0;l<=k1;++l){
                    if(f[  t[p].l ][j][l]!=0x7f7f7f7f&&f[t[p].r][j][k1-l]!=0x7f7f7f7f)

                    f[p][j][k1]=min(f[p][j][k1],f[  t[p].l ][j][l]+f[t[p].r][j][k1-l]+w[p]*(dis[p]-dis[j]) );   
                     //将一个节点的孩子和兄弟都遍历完后才会访问到他,
                    //也就是说 他是原来那棵子树中最左上方的节点 
                }

            }

        }

    }
    cout<<f[t[0].l][0][k];
}

应该自己按照样例画一颗河流树更方便理解,因为加图太麻烦,我就不画了。
——下一道!


HAOI 2015 树上染色 (bzoj 4033)
要算贡献的啊,f[i][j]代表子树i中有j个黑点时对答案的最大贡献,最终答案就是f[root][k]
上代码

//haoi 2015 树上染色 
//bzoj 4033
//by cyd
#include<cstdio>
#include<iostream>
#define N 2010
#define ll long long 
#define max(a,b) (a>b?a:b)
using namespace std;
int n,K,size[N],head[N],cnt;
bool vis[N];
ll f[N][N];
struct edge{
    int to,next;
    ll w;
}e[2*N];
void add(int a,int b,ll w){
    cnt++;
    e[cnt].w=w;
    e[cnt].to=b;
    e[cnt].next=head[a];
    head[a]=cnt;
    return ;
}
void dfs(int k){
    vis[k]=1;
    size[k]=1;
    for(int i=head[k];i;i=e[i].next){
        int t=e[i].to;
        int w=e[i].w;
        if(vis[t])continue;    //防止重复访问(无向图)
        dfs(t);

        for(int j=size[k];j>=0;--j){//  跟背包类似 防止重复使用
            for(int l=size[t];l>=0;--l){//因为j+0=j会影响后来的答案 所以把0放在最后
                if(j+l>K)continue;//小剪枝
                f[k][j+l]=max(f[k][j+l],f[k][j]+f[t][l]+(ll)w*l*(K-l) +(ll)w*(size[t]-l)*(n-K-(size[t]-l)));   //要强转longlong ,不然会少20分  
                    //f[k][j+l]的贡献等于 大树f[k][j]的贡献加上小树f[t][l]的贡献再加上小树并进大树那条边的贡献(贡献等于权值*访问次数)
            }   
        }

        size[k]+=size[t];//  访问后再加
    }
    return ;
}
int main(){
    scanf("%d%d",&n,&K);
    for(int i=1;i<n;++i){
        int a,b;
        ll w;
        scanf("%d%d%lld",&a,&b,&w);
        add(a,b,w);
        add(b,a,w);
    }
    dfs(n);
    cout<<f[n][K];

}

The End.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值