[Codevs]1746 [NOI2002] 贪吃的九头龙 树形Dp + 多叉转二叉

来源:互联网 发布:树莓派ubuntu系统截图 编辑:程序博客网 时间:2024/05/16 18:29

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

输入描述 Input Description
输入文件dragon.in的第1行包含三个整数N (1<=N<=300),M (2<=M<=N),
K (1<=K<=N)。 N 个果子依次编号1,2,…,N,且最大的果子的编号总是1。第2
行到第N行描述了果树的形态,每行包含三个整数a (1<=a<=N),b (1<=b<=N),
c (0<=c<=105),表示存在一段难受值为c的树枝连接果子a和果子b。

输出描述 Output Description
输出文件dragon.out 仅有一行,包含一个整数,表示在满足“大头”的要求
的前提下,九头龙的难受值的最小值。如果无法满足要求,输出-1。

样例输入 Sample Input
8 2 4
1 2 20
1 3 4
1 4 13
2 5 10
2 6 12
3 7 15
3 8 5

样例输出 Sample Output
4

以下是来自同机房oyyl神犇的题解:

注:本题目中所说的最大的果子就是根节点

这道题目应该说是树状dp中比较难的题目了,其中通过树状dp转移时的一些特点,将一棵树从一颗多叉树转化为了二叉树(由于dp状态是通过所有子树状态加和转移,所以同一父亲的兄弟之间的便利顺序就不是特别重要了,而且状态可以累加,所以将一颗多叉树转化为一颗左儿子右兄弟的二叉树也是可以的);

本题目中我们可以先发现一件事情,当有两个即两个以上的小头时,小头便绝对不会吃到树枝:假如我们贪心的在小头没有被选完的情况下,对于下一个果子让没有吃过果子的小头吃果子,那么即能更好的满足让每个小头都吃到果子,有能避免吃树枝的情况,而当所有小头都吃过至少一个果子后,便只需要让父亲与儿子之间不是同一个小头吃的即可,所以可以证明当小头数量大于等于2的时候,小头可以有不吃到树枝的情况。

然而由于题目中又说的是求蓝瘦度最小的情况,那么我们肯定是使小头经量多吃更优,所以我们只让大头刚好吃k个

既然这样,那么当小头数量大于等于2时,本题便至于大头有关了,于是便能容易的想到如何定义状态:

当前结点的状态与两个因素有关:

【1】当前节点是被大头吃还是小头吃
【2】大头在以当前节点为根的子树中吃了多少个果子
那么便很容易写出dp方程:

dp[i][j][0/1] 代表在第i号节点【被吃(1)/ 没被吃(0)】,大头在这颗子树中吃了j个果子;
由于一个节点可能有多个子节点,所以若以多叉树的模式来dp会很恶心(总之我是没写出来的。。。)所以我们可以把它转化为一个二叉树(左儿子右兄弟),由于此题得父亲节点的dp状态是由儿子节点们的dp状态累加起来的,所以可以保证其正确性;

#include<stdio.h>#include<cstring>#include<algorithm>#define deeper(a) memset(a, -1, sizeof(a))using namespace std;const int maxn = 310;const int inf = 1e9;int n, m, k, num;int dp[maxn][maxn][2], h[maxn];struct point{ int ls, rs, w, siz;}p[maxn];struct edge{ int nxt, v, w;}e[maxn * 2];inline void add(int u, int v, int w){    e[++num].v = v, e[num].w = w, e[num].nxt = h[u], h[u] = num;    e[++num].v = u, e[num].w = w, e[num].nxt = h[v], h[v] = num;}void dfs_init(int u, int fa){    p[u].siz = 1;    for(int i = h[u]; i; i = e[i].nxt){        int v = e[i].v;        if(v == fa) continue;        dfs_init(v, u);        p[u].siz += p[v].siz;        p[v].w   = e[i].w;        p[v].rs = p[u].ls;         p[u].ls  = v;    }}int dfs(int u, int amo, int g){    if(amo < 0) return inf;    if(~dp[u][amo][g]) return dp[u][amo][g];    if(!u && !amo)     return dp[u][amo][g] = 0;    if(!u && amo)      return dp[u][amo][g] = inf;    dp[u][amo][g] = inf;    int lim = min(amo, p[u].siz);    for(int i = 0; i <= lim; ++i){        int t1 = dfs(p[u].ls, i - 1, 1) + g * p[u].w;        int t2 = dfs(p[u].ls, i, 0) + (m == 2) * (!g) * p[u].w;        int t3 = dfs(p[u].rs, amo - i, g);        dp[u][amo][g] = min(dp[u][amo][g], min(t1, t2) + t3);    }    return dp[u][amo][g];}int main(){    deeper(dp);    scanf("%d%d%d", &n, &m, &k);    for(int i = 1; i < n; ++i){        int a, b, c;        scanf("%d%d%d", &a, &b, &c);        add(a, b, c);    }    if(n - k < m - 1) {puts("-1"); return 0;}    dfs_init(1, 1);    printf("%d\n", dfs(p[1].ls, k - 1, 1));}
阅读全文
0 0
原创粉丝点击