2018.07.07【2018提高组】模拟B组

哈,树形DP专题来的
树形dp,一般是把树用dfs遍历一遍,然后根据子节点的状态转移出根节点的状态

0 【CQOI2009】叶子的颜色【推荐】

 给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部
 结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少
 包含一个有色结点(哪怕是这个叶子本身)。
  对于每个叶结点u,定义c[u]为从u到根结点的简单路径上第一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。
  
题目中任何非叶子节点为根得出的答案都一样,所以随便选一个根就好
设f[i][1/0]表示以i为根的子树中,i染成1/0(黑色或白色)时,最少的着色节点数
首先,每个节点的初始值为1,因为染色后代价为1
若i染成黑色,儿子也是黑色,那么f[i][1]+=f[son][1]-1,结果要加起来,因为父亲节点的代价应为每个
子节点的和,若不同色,那么直接把子节点的代价加入父亲节点

#include <cstdio>
#include <algorithm>

using namespace std;

int n,m,cnt,c[100005],f[100005][2];
int ls[100005],ne[100005],jy[100005];

void init(int x,int y){
    ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
}

void dfs(int x,int fa){
    int t=ls[x];
    if (!f[x][0]) f[x][0]=f[x][1]=1;
    while (t){
        if (jy[t]!=fa){
            dfs(jy[t],x);
            f[x][0]+=min(f[jy[t]][0]-1,f[jy[t]][1]);
            f[x][1]+=min(f[jy[t]][1]-1,f[jy[t]][0]);
        }
        t=ne[t];
    }
}

int main(){
    scanf("%d%d",&m,&n);
    for (int i=1;i<=n;i++){
        int e;
        scanf("%d",&e);
        f[i][e]=1;f[i][e^1]=1000000;
    }
    for (int i=1;i<m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        init(x,y);init(y,x);
    }
    dfs(m,0);
    printf("%d",min(f[m][1],f[m][0]));
}

1 【NOI2002】贪吃的九头龙

传说中的九头龙是一种特别贪吃的动物。虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落。
有一天,有M个脑袋的九头龙看到一棵长有N个果子的果树,喜出望外,恨不得一口把它全部吃掉。可是必须照顾到每个头,因此它需要把N个果子分成M组,每组至少有一个果子,让每个头吃一组。
这M个脑袋中有一个最大,称为“大头”,是众头之首,它要吃掉恰好K个果子,而且K个果子中理所当然地应该包括唯一的一个最大的果子。果子由N-1根树枝连接起来,由于果树是一个整体,因此可以从任意一个果子出发沿着树枝“走到”任何一个其他的果子。
对于每段树枝,如果它所连接的两个果子需要由不同的头来吃掉,那么两个头会共同把树枝弄断而把果子分开;如果这两个果子是由同一个头来吃掉,那么这个头会懒得把它弄断而直接把果子连同树枝一起吃掉。当然,吃树枝并不是很舒服的,因此每段树枝都有一个吃下去的“难受值”,而九头龙的难受值就是所有头吃掉的树枝的“难受值”之和。
九头龙希望它的“难受值”尽量小,你能帮它算算吗?
例如图1所示的例子中,果树包含8个果子,7段树枝,各段树枝的“难受值”标记在了树枝的旁边。九头龙有两个脑袋,大头需要吃掉4个果子,其中必须包含最大的果子。即N=8,M=2,K=4:

当m>=3时,总难受值就是大头的难受值,因为剩下的最少也有两个头,每段树枝都可以分给两个头
各吃一边;当m=2时,如果大头不吃就只能分给小头,所以当大头有连续的两个果子不吃时,连接两
个果子的树枝的难受值也应计入答案。而-1的情况很好判断。
设f[i][j][1/0]表示第i个点,吃了j个果子,当前果子吃不吃(1/0)的难受值
由于方程中出现了”–”这种东西,所以要么j倒着循环,要么重开一个数组记录f[i]的值,用这个数组更新答案

f [ i ] [ j ] [ 1 ] = m i n ( f [ s o n ] [ k ] [ 1 ] + f [ i ] [ j k ] [ 1 ] + w [ i ] [ s o n ] , f [ s o n ] [ k ] [ 0 ] + f [ i ] [ j k ] [ 1 ] )

f [ i ] [ j ] [ 0 ] = m i n ( f [ s o n ] [ k ] [ 1 ] + f [ i ] [ j k ] [ 0 ] , f [ s o n ] [ k ] [ 0 ] + f [ i ] [ j k ] [ 0 ] + w [ i ] [ s o n ] ( m == 2 ) )

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

int n,m,cnt,k;
int f[610][610][2],w[610][610];
int ne[610],ls[610],jy[610];

void dfs(int x,int fa){
    f[x][1][1]=f[x][0][0]=0;
    for (int t=ls[x];t;t=ne[t])
    if (jy[t]!=fa){
        int y=jy[t];
        dfs(y,x);   
        int ff[310][2];
        memcpy(ff,f[x],sizeof(ff));
        memset(f[x],0x3f,sizeof(f[x]));
        for (int i=0;i<=k;i++){     
            for (int j=0;j<=k&&j<=i;j++){
                f[x][i][1]=min(f[x][i][1],
                min(f[y][j][1]+ff[i-j][1]+w[x][y],f[y][j][0]+ff[i-j][1]));
                f[x][i][0]=min(f[x][i][0],
                min(f[y][j][1]+ff[i-j][0],f[y][j][0]+ff[i-j][0]+w[x][y]*(m==2)));
            }
        } 
    }
}

void init(int x,int y){
    ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
} 

int main(){
    scanf("%d%d%d",&n,&m,&k);
    if (n-k<m-1) {printf("-1");return 0;}
    for (int i=1;i<=n-1;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        init(a,b);init(b,a);
        w[a][b]=w[b][a]=c;
    }
    memset(f,0x3f,sizeof(f));
    dfs(1,0);
    printf("%d",f[1][k][1]);
}

2 【GDOI2005】寻宝之旅【难】

探险队长凯因意外的弄到了一份黑暗森林的藏宝图,于是,探险队一行人便踏上了寻宝之旅,去寻找传说中的宝藏。
藏宝点分布在黑暗森林的各处,每个点有一个值,表示藏宝的价值。它们之间由一些小路相连,小路不会形成环,即两个宝藏点之间有且只有一条通路。探险队从其中的一点出发,每次他们可以留一个人在此点开采宝藏,也可以不留,然后其余的人可以分成若干队向这一点相邻的点走去。需要注意的是,如果他们把队伍分成两队或两队以上,就必须留一个人在当前点,提供联络和通讯,当然这个人也可以一边开采此地的宝藏。并且,为了节约时间,队伍在前往开采宝藏的过程中是不会走回头路的。现在你作为队长的助理,已经提供了这幅藏宝图,请你算出探险队所能开采的最大宝藏的价值。

设f[i][j][1/0]表示在第i个点,用了j个人,在当前点是否留一人开采宝藏(1/0),可获得的最大价值
一般这些用了j的代价的状态表示,都要给j两个循环,枚举当前节点代价和子节点代价

f [ i ] [ j ] [ 1 ] = m a x ( f [ s o n ] [ k ] [ 1 ] , f [ s o n ] [ k ] [ 0 ] ) + f [ i ] [ j k ] [ 1 ]

f [ i ] [ j ] [ 0 ] = m a x ( f [ s o n ] [ j ] [ 1 ] , f [ s o n ] [ j ] [ 0 ] )

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;

int n,m,cnt,a[118];
int f[118][118][2],bz[118];
int ls[218],ne[218],jy[218];

void dfs(int x,int fa){
    for (int i=1;i<=m;i++)
        f[x][i][1]=a[x];
    for (int t=ls[x];t;t=ne[t])
    if (jy[t]!=fa){
        dfs(jy[t],x);
        for (int j=1;j<=m;j++)
            f[x][j][0]=max(f[x][j][0],max(f[jy[t]][j][0],f[jy[t]][j][1]));
        for (int j=m;j>=1;j--)  
            for (int k=1;k<=j;k++)
                f[x][j][1]=max(f[x][j][1],
                f[x][k][1]+max(f[jy[t]][j-k][1],f[jy[t]][j-k][0]));
    }
}

void init(int x,int y){
    ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) 
        scanf("%d",&a[i]);
    for (int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        init(x,y);init(y,x);
    }
    bz[1]=1;
    dfs(1,0);
    printf("%d",max(f[1][m][0],f[1][m][1]));
}

猜你喜欢

转载自blog.csdn.net/yjy_aii/article/details/80973856
今日推荐