ACM: 动态规划题 黑书-…

这篇博客介绍了如何帮助一条九头龙在满足其最大头必须吃特定果子的情况下,合理分配果树上的果子,使得所有头吃掉树枝的难受值之和最小。通过转化为二叉树结构,利用动态规划求解问题,具体解题思路和代码实现被详细阐述。

贪吃的九头蛇

【问题描述】

传说中的九头龙是一种特别贪吃的动物。虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落。

有一天,有M个脑袋的九头龙看到一棵长有N个果子的果树,喜出望外,恨不得一口把它全部吃掉。可是必须照顾到每个头,因此它需要把N个果子分成M组,每组至少有一个果子,让每个头吃一组。

这M个脑袋中有一个最大,称为“大头”,是众头之首,它要吃掉恰好K个果子,而且K个果子中理所当然地应该包括唯一的一个最大的果子。果子由N-1根树枝连接起来,由于果树是一个整体,因此可以从任意一个果子出发沿着树枝“走到”任何一个其他的果子。

对于每段树枝,如果它所连接的两个果子需要由不同的头来吃掉,那么两个头会共同把树枝弄断而把果子分开;如果这两个果子是由同一个头来吃掉,那么这个头会懒得把它弄断而直接把果子连同树枝一起吃掉。当然,吃树枝并不是很舒服的,因此每段树枝都有一个吃下去的“难受值”,而九头龙的难受值就是所有头吃掉的树枝的“难受值”之和。

九头龙希望它的“难受值”尽量小,你能帮它算算吗?

例如图1所示的例子中,果树包含8个果子,7段树枝,各段树枝的“难受值”标记在了树枝的旁边。九头龙有两个脑袋,大头需要吃掉4个果子,其中必须包含最大的果子。即N=8,M=2,K=4:

ACM: <wbr>动态规划题 <wbr>黑书-贪吃的九头龙

图一描述了果树的形态,图二描述了最优策略。


【输入文件】

输入文件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。

【输出文件】

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

【样例输入】

8 2 4
1 2 20
1 3 4
1 4 13
2 5 10
2 6 12
3 7 15
3 8 5

8 3 4
1 2 20
1 3 4
1 4 13
2 5 10
2 6 12
3 7 15
3 8 5

2 2 1
1 2 10

2 2 2
1 2 10

【样例输出】

4

0

0

-1


题意: 一条九头龙的动物, 有M个脑袋, 每个脑袋都必须吃到果子, 一棵有N个果子的树, 分配给它每个头吃,

      其中一个最大的头要吃K个果子, 其余分配给其它的头, 如果一个头同时吃到相邻的果子会有一个难受

      值, 现在要你分配果子使得难受值的和最小.

解题思路: (黑书思路)

        1. 无解情况, 果子不够吃, N<K+M-1

        2. 当M=2时, 大头要不吃到相邻的要不吃不到. 当M>=3时, 确定大头吃掉之后, 剩下的果子按照果树

           高度奇偶分配即可. 可以确定问题: 当M=2时, 难受值=两端果子被大头或小头吃的难受值之和.

           当M>=3时, 两端的果子都被大头吃掉的难受值之和.(因为问题有解, 所以剩下一定满足不相邻分配)

        3. 为了简化问题(减少决策), 将多叉树转化成二叉树结构(左孩子右兄弟结构).

            设状态: dp[i][j][k]: 表示以i节点为根的子树有j个果子分配给大头吃的最小难受值. 其中,

            k=0表示fa[i]被大头吃, k=1表示fa[i]被小头吃.

            方程:  dp[i][j][k] = min(

                                    dp[X1][j1][1]+dp[X2][j-j1][k] + judge(k,1)*cost(i, fa[i]);

                                    dp[X1][j1][0]+dp[X2][j-j1-1][k] + judge(k,0)*cost(i, fa[i]);

                                            );

            X1:是i节点的字节点, X2:是i节点的兄弟节点(父亲被吃情况相同时k); judge判断k与1是否相同.

            judge(k1, k2): (k1 == 1&&k2 == 1  ==> judge(k1,k2) = 1)

                           (k1 == 0&&k2 == 0&&M==2 ==> judge(k1, k2) == 1)

                           other judge(k1, k2) == 0;

            边界: dp(0,0,k) = 0; dp(0,j,k) = INF(无穷大, 表示情况不成立, j>0);


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define MAX 305
const int INF = (1<<29);

struct node
{
    int v;
    int next;
}edges[MAX*2];

int n, m, K;
int dp[MAX][MAX][2];
int fa[MAX], son[MAX], bro[MAX], ch[MAX];
int first[MAX], num;
int cost[MAX][MAX];

inline int min(int a, int b)
{
    return a < b ? a : b;
}

inline void add(int u, int v)
{
    edges[num].v = v;
    edges[num].next = first[u];
    first[u] = num++;
}

void readGraph()
{
    memset(dp, -1, sizeof(dp));
    memset(first, -1, sizeof(first));
    memset(son, 0, sizeof(son));
    memset(bro, 0, sizeof(bro));
    memset(cost, 0, sizeof(cost));
    num = 0;
    for(int i = 1; i <= n; ++i)
    {
        ch[i] = 1;
        fa[i] = i;
    }
    int u, v, w;
    for(int i = 1; i < n; ++i)
    {
        scanf("%d %d %d", &u, &v, &w);
        cost[u][v] = cost[v][u] = w;
        add(u, v);
        add(v, u);
    }
}

void makeGraph(int u, int f) //转换成二叉树
{
    fa[u] = f;
    int *point = &son[u];
    for(int e = first[u]; e != -1; e = edges[e].next)
    {
        int v = edges[e].v;
        if(v == f) continue;
        *point = v;
        point = &bro[v];
        makeGraph(v, u);
    }
}

inline int judge(int i, int j)
{
    if(i == 1 && j == 1) return 1;
    else if(i == 0 && j == 0 && m == 2) return 1;
    else return 0;
}

int dfs(int x)
{
    if( !son[x] && !bro[x] ) return ch[x];
    return ch[x] += dfs(son[x])+dfs(bro[x]);
}

int DP(int i, int j, int k)
{
    if(j < 0) return INF;
    if(dp[i][j][k] != -1) return dp[i][j][k];
    if(j > ch[i]) return dp[i][j][k] = INF;
    if(i == 0 && j == 0) return dp[i][j][k] = 0;
    if(i == 0) return dp[i][j][k] = INF;
   
    int ans = INF;
    int temp1 = INF, temp2 = INF;
    for(int t = 0; t <= j; ++t)
    {
        temp1 = DP(son[i], t, 1)+DP(bro[i], j-t, k)+judge(k,1)*cost[i][fa[i]]; //小头吃i
        temp2 = DP(son[i], t, 0)+DP(bro[i], j-t-1, k)+judge(k,0)*cost[i][fa[i]]; //大头吃i
        ans = min(ans, min(temp1, temp2));
    }
   
    return dp[i][j][k] = ans;
}

int main()
{
    freopen("input.txt", "r", stdin);
    while(scanf("%d %d %d", &n, &m, &K) != EOF)
    {
        readGraph();
        makeGraph(1, 1);
       
        dfs(1);
        if(n < K+m-1) printf("-1\n");
        else printf("%d\n", DP(son[1], K-1, 0));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值