POJ 2057 The Lost House 经典树形DP+贪心

题意:链接

方法:树形DP+贪心

解析:这是一道好题。

好首先要明确这题求的是什么?

名义上是期望值,而实际上就是找一条路径。什么路径呢?从根节点走遍所有的叶子节点所花费步数最短的路径。

明确了题意后该怎么做呢?

首先看我们需要什么?

目前有个根节点,我们需要知道从他向一个分支走,失败步数是多少,成功步数是多少?

那么怎么维护我们需要的东西呢?

首先我们先给他们起个名:suc,fai;

其次再给一个节点的叶子节点的个数起个名:son

起名完事之后我们就要更新了。

先谈叶子节点,显然叶子节点的suc[x]=0,fai[x]=0,son[x]=1;

之后就是向上更新了,son和fai也很好搞

对于son的更新son[fa]+=son[x];

对于fai的更新fai[fa]+=fai[x]+2(此时worm[x]=0);否则的话不用管。

对于最不好弄的suc更新:

首先对于我们当前讨论的要走的子节点pn,这时候,蜗牛已经经过了走p1~pn-1的失败的步数,所以这些失败的步数是要记录的,我们给它命名为cnt-fai,此时我们会多走几个cnt-fai呢?我们发现,一共会多走son[pn]个cnt-fai,然而每次走过一个叶子节点后,更新一次suc[x],然后再将其视为失败,返回,找下一个叶子节点,此时我们就会发现,再返回到px的时候,这个蜗牛还需要继续返回一层,即返回到目前的根节点x,多走出一步,多走多少个一步呢?仍然是son[pn]个,用公式来写就是这样: suc[x]+=(cntfai+1)son[pn]+suc[pn]

同时更新cntfai, cntfai+=fai[pn]+2 这个2就是指去以及回来的两步

以上,dp的部分差不多搞定了,观察上面的式子发现,唯一不能确定的就是pn是什么鬼?

也就是说,对于一个根节点x我们按照什么顺序来讨论他的子节点会使得x的suc最小呢?

以下引用discuss里某神犇的证明

假设交换相邻的两颗子树的选择顺序,设P1,P2为选他们的概率,A1,A2为房子确实在上面所需的步数,B1,B2为实际上不在上面所需的步数,则
调整后:Delta=P1*A1+P2*(B1+A2)-P2*A2-P1*(B2+A1)=P2*B1-P1*B2
于是Delta<0 <=> B1/P1小于B2/P2
而题设情况即为Delta<0
于是应按照B/L排序 即遍历此子树所需步数/其所含叶子树

转化为公式呢?就是这个东西

(fai[u]+2)son[v]<(fai[v]+2)son[u]

其中u和v分别是两个子节点。

后记:这题是道好题,做完后能学到不少东西,值得一做,然而自己在做的时候对于这种双线的把握还不是很好,比较欠缺,什么鬼的排序根本没想到,自己对于这种难题的把握还是差很多啊!

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1100
using namespace std ;
int n,cnt;
bool col[N];
int head[N],suc[N],fai[N],son[N];
char s[5];
struct node
{
    int to,next;
}edge[N];
void init()
{
    memset(head,-1,sizeof(head));
    memset(col,0,sizeof(col));
    memset(suc,0,sizeof(suc));
    memset(fai,0,sizeof(fai));
    memset(son,0,sizeof(son));
    cnt=1;
}
int cmp(int u,int v)
{
    return (fai[u]+2)*son[v]<(fai[v]+2)*son[u];
}
void edgeadd(int from,int to)
{
    edge[cnt].to=to;
    edge[cnt].next=head[from];
    head[from]=cnt++;
}
void dfs(int u)
{
    if(head[u]==-1)
    {
        suc[u]=0,son[u]=1,fai[u]=0;
    }
    int tmp[N],tot=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        tmp[++tot]=to;
        dfs(to);
        son[u]+=son[to];
        if(!col[u])fai[u]+=fai[to]+2; 
    }
    sort(tmp+1,tmp+1+tot,cmp);
    int cnt_fai=0;
    for(int i=1;i<=tot;i++)
    {
        suc[u]+=(cnt_fai+1)*son[tmp[i]]+suc[tmp[i]];
        cnt_fai+=fai[tmp[i]]+2;
    }
}
int main()
{
    while(scanf("%d",&n)&&n!=0)
    {
        init();
        for(int i=1;i<=n;i++)
        {
            int pre;
            scanf("%d%s",&pre,s);
            col[i]=s[0]=='Y'?1:0;
            if(pre==-1)continue;
            edgeadd(pre,i);
        }
        dfs(1);
        printf("%.4lf\n",(double)suc[1]/(double)son[1]);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值