POJ 1694 古老的游戏

【问题描述】

  有一个古老的石头游戏,该游戏基于任意一棵树T,游戏的目标是在树T的根节点上放一颗石头,游戏的规则如下:

  1、 游戏开始前,玩家先将K个石头放入桶中。

  2、 在游戏的每一步,玩家从桶中拿一颗石头放到树的一个空的叶子节点上。

  3、 当一个节点p的所有r个子节点都有一个石头,则移去r个子节点上的石头,然后将一个石头放到节点p上。剩下的r-1个石头重新放到桶中重复使用。

  如果玩家根据以上规则放置石头,并最终在根节点上放置一颗石头,则赢得游戏。

  现在的任务是求出游戏开始时,石头数K的最小值,使得玩家能在给定树的情况下赢得游戏。

【输入格式】

  第一行输入测试用例的个数T,每个测试用例是一颗树的描述。第二行开始输入每棵树的描述。
  每棵树有N个节点,节点标号为1,2,…N,每个节点可以有任意个子节点,根节点标号为1,。树的描述第一行为节点数N,第二行开始按照节点标号顺序描述N个节点的子节点,每行第一个数为标号p,第二个数为子节点数r,如果r不为0,接下来为r个子节点的标号。

【输出格式】

  对每棵树,输出石头数的最小值。

【输入样例】

2
7
1 2 2 3
2 2 5 4
3 2 6 7
4 0
5 0
6 0
7 0
12
1 3 2 3 4
2 0
3 2 5 6
4 3 7 8 9
5 3 10 11 12
6 0
7 0
8 0
9 0
10 0
11 0
12 0

【输出样例】

3
4

【数据范围】

1<=T<=10 N<200

题意:

给出一棵一般的树,按如下的三种规则在树上放石子:

1,在游戏开始时,玩家可以拿K个石子放在水桶里。

2,在游戏的每一步,玩家可以从水桶里拿出一个石子,并放在任意一个空的叶子上。

3,当一个父节点的r个子节点都被放上了一个石子,可以将这r个石子都拿去并在父节点上放一个石子。水桶里石子可以再次使用。

显然,最终根上会被放上一个石子,问的是,求用最少的石子达到上述要求,求这个最小值。(有很多k,求最小的那个);

思路:

  设所求结果用result来表示,我们要求的是以1为根的result,这个结果必定依赖于它的子节点的相关信息。这个信息会是什么呢?

  考虑一棵树的子孙是该树的子树。假设1的子孙分别为r1,r2,r3。那么r1r2r3,都是1的子树的根。假设以r1r2r3为根的上述结果分别为result1,result2,result3,显然它们之和一定能达到1上也能被放上石子的目的。那这个值是不是最小的呢?假设result1>=result2>=result3,我们用result1个石子让r1上放上了一个石子,此时我们剩余了result1-1个石子没有用,这些石子可以弥补result2,最终可能会得到一个更小的值(<=result1+result2+result3,通过对先前石子的重复利用)。如何安排这种弥补方式呢?

  最大值优先。r1需要的石子result1最大,那么最终的结果必定result>=result1,然而最终只需在r1上放一个石子,留下了result1-1个石子。

那么r1r2一共需要Result = result1+result2-min( result-1,result2 )个石子,其中min(result1-1,result2)被加了两次,所以要减掉一次。

这样r1r2就合并成了一个节点,最少需要Result个石子,那么result=Result+result3-min(Result-2,result3)。最终得解。

  显然这是一个贪心算法,符合贪心选择和最优子结构
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<cstring>
#define mod 10007
#define maxn 505 
#define oo 100000000
using namespace std;
int n,T;
int sz[maxn],d[maxn],g[maxn][maxn];

void read(int &x){
    x=0;
    bool ok=0;
    char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    while((ch>='0'&&ch<='9')||ch=='-')
    {
        if(ch=='-') ok=1;
        else x=x*10+ch-'0';
        ch=getchar();
    }
    if(ok) x=-x;
}

void dfs(int i)
{
    if(sz[i]==0)
    {
        d[i]=1;
        return ;
    }
    int cnt=0,a[300];
    for(int k=1;k<=sz[i];k++)
    {
        int j=g[i][k];
        dfs(j);
        a[++cnt]=d[j];
    }
    sort(a+1,a+cnt+1);
    int num=a[cnt]-1,sum=a[cnt];
    for(int k=cnt-1;k>=1;k--)
    if(a[k]>num) sum+=(a[k]-num);
    d[i]=sum;
}

void task()
{
    dfs(1);
    printf("%d\n",d[1]);
}

void in()
{
    read(T);
    while(T--)
    {
        read(n);
        for(int i=1;i<=n;i++)
        {
            int excuse_me;
            read(excuse_me);read(sz[excuse_me]);
            for(int j=1;j<=sz[excuse_me];j++) read(g[excuse_me][j]);
        }
        task();
    }
}

int main()
{
    freopen("in.txt","r",stdin);
    in();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值