[NOI2002] 贪吃的九头龙

题目类型:树形DP

传送门:>Here<

题意:有一只九头龙要吃了一颗树,给出一棵\(N\)个节点的带边权的树。九头龙有\(M\)个头,其中一个是大头,大头要吃恰好\(K\)个节点,其他头吃几个随意。如果一个头吃了一个连通块,那么他们会把树枝也吃下去,获得边权那么多的难受值。先要吃完整棵树,使难受值总和最小

解题思路

首先会发现题目蕴含着一个奇妙的性质。会得到一条边的难受值当且仅当一条边相邻的两个节点是被同一个头吃的,换句话说如果相邻的两个节点是不同的龙吃的那么就不会获得该边的难受值。当我们确定大头吃的\(K\)个点之后,由于剩下的头每个头想吃几个就吃几个,由于给出了一颗树,一定存在一种方案使得任意两个相邻的点是不同的头。事实上,只需要多余的两种头就可以了。换句话说,我们可以把所有多余\(3\)个头的情况看做是\(3\)个头的。

当然需要特殊考虑\(M=2\)的情况,此时剩余的只有一个头了,需要特殊处理

于是,难受值完全取决于大头的那\(K\)个节点如何选择。

考虑树形DP:\(f[u][j][0/1]\)表示子树\(u\)中大头吃\(j\)个的最小难受值之和,其中\(k=0\)表示大头不吃根节点,反之亦然

于是可以发现我们只需要判断\(u,v\)的颜色是否相同即可以转移:当\(M>2\)时,除非\(u,v\)都是\(1\),否则不考虑。当\(M=2\)时,\(0,1\)可以直接表示他们的头了,所以也就是\(u ⊕ v\)

于是我们可以初步得到转移方程:\[f[u][j][1]=Min\{f[v][k][0]+g[u][j-k][1],f[v][k][1]+g[u][j-k][1]+cost(u,v)\}\]\[f[u][j][0]=Min\{f[v][k][0]+g[u][j-k][0]+cost(u,v)*[M=2],f[v][k][1]+g[u][j-k][0]\}\]

注意方程中的\(g\)数组,我们好像并没有涉及到它。事实上我们发现,方程的转移时需要枚举\(k\)作为中介的,但是众多儿子,每个儿子的\(k\)如何分配?题目给出了一颗多叉树,使得这一步非常复杂。

联想二叉树的做法,二叉树时当确定一个儿子是\(k\)时,马上就能确定另一个儿子是\(j-k\)。如果也用这种方式来处理多叉树就好了。于是我们在考虑第\(j\)个儿子的时候,可以记录前\(j-1\)个已经做过的儿子的最优值。而上一轮留下的结果正是\(f\)数组!但由于\(f\)数组在不断的更新,肯定不能直接拿过来用,因此需要一个临时数组\(g\)来记录上一轮的结果,其实形象的理解,就是把前面的\(j-1\)个儿子并在了一起

由于每一轮\(f\)数组都要重新更新,因此每一轮都要重新赋值\(+∞\)。另外很容易得到另个初始化条件:\(f[u][0][0]=1, f[u][1][1]=0\)。意义都很显然

Code

/*By DennyQi 2018.8.20*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int MAXN = 310;
const int MAXM = 610;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();return x * w;
}
int N,M,K,x,y,z;
int first[MAXM],nxt[MAXM],to[MAXM],cost[MAXM],cnt;
int f[MAXN][MAXN][2], g[MAXN][2];
inline void add(int u, int v, int w){
    to[++cnt]=v, cost[cnt]=w, nxt[cnt]=first[u], first[u]=cnt;
}
void DP(int u, int _f){
    int v;
    f[u][0][0] = f[u][1][1] = 0;
    for(int i = first[u]; i; i = nxt[i]){
        if((v = to[i]) == _f) continue;
        DP(v, u);
        for(int j = 0; j <= K; ++j){
            g[j][0] = f[u][j][0];
            g[j][1] = f[u][j][1];
            f[u][j][0] = INF;
            f[u][j][1] = INF;
        }
        for(int j = 0; j <= K; ++j){
            for(int k = 0; k <= j; ++k){
                f[u][j][1] = Min(f[u][j][1], f[v][k][0]+g[j-k][1]);
                f[u][j][1] = Min(f[u][j][1], f[v][k][1]+g[j-k][1]+cost[i]);
                if(M == 2){
                    f[u][j][0] = Min(f[u][j][0], f[v][k][0]+g[j-k][0]+cost[i]);
                }
                else{
                    f[u][j][0] = Min(f[u][j][0], f[v][k][0]+g[j-k][0]);
                }
                f[u][j][0] = Min(f[u][j][0], f[v][k][1]+g[j-k][0]);
            }
        }
    }
}
int main(){
    N = r, M = r, K = r;
    if(M-1 + K > N){
        printf("-1");
        return 0;
    }
    for(int i = 1; i < N; ++i){
        x = r, y = r; z = r;
        add(x, y, z);
        add(y, x, z);
    }
    memset(f, 0x3f, sizeof(f));
    DP(1, 0);
    printf("%d", f[1][K][1]);
    return 0;
}

转载于:https://www.cnblogs.com/qixingzhi/p/9506086.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值