poj 2057 树形DP+一点贪心

这是一道好题啊。感觉学到了不少思想和技巧。

下面参考了这个链接,我感觉讲清楚了:http://blog.sina.com.cn/s/blog_5f5353cc0100hd08.html

由于总叶子数是固定的,那么要算最小期望,只要算出到各个叶结点的步数和的最小值即可。

success[i],表示在i为根的子树上成功找到House的步数和,当i为叶子时,Success[i]=0(这时一步都不用走)

failure[i],表示在i为根的子树上找不到House的步数,当worm[i]=1或者i为叶子时,那么Failure[i]=0(原因同上)

leaves[i],表示i为根的子树上叶子节点的数目,当为叶子时,Leaves[i]=0

在树的最低层i,自然Failure[i]=0,但它的father自然是Failure[father]=Failure[i]+2(走到I一步,走回去一步,所以是两步),而当结点father有worm时,则为Failure[father]=0;同样我们可以推出最树低的叶子Success[i]=0,而father与其他儿子有关,不单单就一个i儿子,那么画图,假设father有2个儿子,其中i儿子是失败的,那么另一个儿子k就是成功,但是先访问i,所以得出Success[father]=(Failure[father]+1)*Leaves[k]+Success[k]。比较费解的就是(Failure[father]+1)*Leaves[k]了。

先看图(用的是题目中的例子):

      1

     \

    3(4)

\

4(1)  5(3)

先假设3为根,那么遍历4和5的步数1+3=4;那么如果1是根,这时要遍历4和5的步数是2+4=6,多一条边,使到4到的步数从1变为2,使到5的步数从3变为4。加起来就比原来多了2。也就是说如果有N个叶结点的话,那么应该多1*N步了。那么如果先从1到2,再从2回到1,之后再走呢。这时应该多了failure[1到2]*N步了。这就解释了红色那部分(红色中的那个failure[father]从代码中可以看出是走K前失败的步数).

显然,su[x]和x儿子的顺序是有很大关系的。

第一种想法是枚举所有的全排列。虽然每个节点只有最多8个儿子,但8!=40320,太大。

第二种想法是贪心。我们可以使用调整的思想来确定儿子的顺序。

 

设y1,y2为x的两个相邻的儿子。若y1在y2之前,则ans1=(fail[x]+1)*le[y1]+su[y1]+(fail[x]+2+fail[y1]+1)*le[y2]+su[y2]

若交换y1,y2,则有ans2=(fail[x]+1)*le[y2]+su[y2]+(fail[x]+2+fail[y2]+1)*le[y1]+su[y1]

则ans1-ans2=(fail[y1]+2)*le[y2]-(fail[y2]+2)-le[y1]。

所以可以根据这个排序。

 

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAX 1111
int tree[MAX][10];   //tree[k][i]表示第k个节点的第i个孩子。tree[k][0]表示节点k的孩子的个数。这个数组相当于建树
int fail[MAX],su[MAX],le[MAX];
bool worm[MAX];
int n,m;
char c;
bool cmp(int a,int b)
{
    return (fail[a]+2)*le[b]<(fail[b]+2)*le[a];
}
void dp(int k)  //自底向上递推
{
    int i,j;
    if(tree[k][0]==0)  //叶子节点情况的初始化。
    {
        le[k]=1;
        fail[k]=0;
        su[k]=0;
        return ;
    }
    for(i=1;i<=tree[k][0];i++)   //初始化子节点
    {
        dp(tree[k][i]);
        le[k]+=le[tree[k][i]];
    }
    sort(tree[k]+1,tree[k]+tree[k][0]+1,cmp);  //贪心排序
    for(i=1;i<=tree[k][0];i++)
    {
        int son=tree[k][i];
        su[k]+=(fail[k]+1)*le[son]+su[son];
        fail[k]+=fail[son]+2;
    }
    if(worm[k]) fail[k]=0;
}
int main()
{
    int i,j;
    while(scanf("%d",&n),n)
    {
        memset(worm,0,sizeof(worm));
        memset(fail,0,sizeof(fail));
        memset(su,0,sizeof(su));
        memset(tree,0,sizeof(tree));
        memset(le,0,sizeof(le));
        for(i=1;i<=n;i++)
        {
            scanf("%d %c",&m,&c);
            if(m!=-1)
              tree[m][++tree[m][0]]=i;
            if(c=='Y') worm[i]=1;
        }
        dp(1);
        printf("%.4lf\n",1.0*su[1]/le[1]);
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值