pku 2057 The Lost House 树形dp+背包dp 解题报告

 

pku 2057 The Lost House 解题报告

一、题目大意:

蜗牛的房子遗失在了一棵树的某个叶子结点上,它要从根结点出发开始寻找它的房子。有一些中间结点可能会住着一些虫子,这些虫子会告诉蜗牛它的房子是否在以这个中间结点为根的子树上,这样蜗牛就不用白跑路了。当然,如果有些结点没有住着虫子的话,那么可怜的蜗牛只有靠自己决定访问顺序来探索了。假设蜗牛走过一条边的耗费都是1,且房子遗失在每个叶子结点的概率都是相等的,那么请问蜗牛找到他的房子的最小数学期望值?

我们约定,树上的结点数n最多为1000,每个结点的分叉数k最多为8

例如在sample的这棵树当中:

蜗牛从根结点1出发开始寻找它的房子,它的房子可能遗失在245。在结点3上住着一只虫子,它会告诉蜗牛,以3为根的子树上是否有蜗牛的房子。蜗牛有两种走法。蜗牛可以先访问2,如果它在那儿不能找到房子,那么它要回到根结点1,再通过3来访问结点4(或5),如果还是不能找到它的房子,那么它又要回到结点3,再去访问结点5(或4)。在这种走法中,当房子分别位于245的时候,蜗牛需要走的步数分别是146,期望值是(1+4+6)/3=11/3。显然,这种走法没有充分发挥虫子在这里起到的作用。在另一种走法中,蜗牛先访问结点3,它可以从住在3上的虫子那里得知它的房子是否存在于45的信息。在这种走法中,当房子分别位于245的时候,蜗牛需要走的步数分别是324,期望值是(3+2+4)/3=3。这种走法合理的利用了虫子提供的信息,得到了更优的数学期望值。

二、算法:

看到此题很容易便知道用树形dp,而我之前有总结过树形dp的大概解题步骤: 

1.具有树形结构;(显而易见)选择一个的容易深搜的树形数据结构.

 2.由底向上dp;通常都是先利用深度优先搜索的算法,搜索到树的最低,而树的最低dp值都是初始化的值.

 3.选择一个好的状态转移方程.此步很重要,也是一般解决dp的普遍规律.

那么我们关键在于找出此题的状态转移方程.我们从题中的最优解想想其状态转移方程。可我就被这难道了,参考了大牛黄劲松2006年的论文才明白这状态转移方程:

int Success[M]; //表示在i为根的子树上成功找到House的期望步数和,i为叶子时,Success[i]=0

int Failure[M]; //表示在i为根的子树上找不到House的步数,worm[i]=1或者i为叶子时,那么Failure[i]=0

int Leaves[M];  //i为根的子树上叶子节点的数目,当为叶子时,Leaves[i]=0

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

其状态转移方程:

(其实经过画图就可以分析得出其状态转移方程,以后自己练习dp要注意这方面的练习才行)

Failure[now] += (Failure[Tree[now][i]] + 2);

Success[now] += (k + 1) * Leaves[temp[i - 1]] + Success[temp[i - 1]];

k += Failure[temp[i - 1]] + 2;

可这并不能马上得出最优解,因为访问now的儿子的顺序并不知道,因为访问儿子顺序不同结果也就不同,像题中就有说到.所以我们在这就可以想想利用公式如何取得其顺序.now的儿子Success各相减,将发现那个步数更小,当然我们需要较小值优先,于是我们用到了贪心.此时这个算法就明了了。

三、AC代码:

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <iostream>

#include <algorithm>  

using namespace std;

#define M 1015

 

int N;

int worm[M];    //标记是否有worm

int Tree[M][M]; //链接表   

int Success[M]; //表示在i为根的子树上成功找到House的期望步数和,i为叶子时,Success[i]=0

int Failure[M]; //表示在i为根的子树上找不到House的步数,worm[i]=1或者i为叶子时,那么Failure[i]=0

int Leaves[M];  //i为根的子树上叶子节点的数目,当为叶子时,Leaves[i]=0

 

bool cmp(const int &u, const int &v)

{

       return (Failure[u] + 2) * Leaves[v] < (Failure[v] + 2) * Leaves[u];

}

 

void solve(int now)

{

       int i, k;

       //从最树低初始化

       if (Tree[now][0] == 0)

       {

              Leaves[now] = 1;

              Failure[now] = 0;

              Success[now] = 0;

              return;

       }

       //从最树低往上dp,保存3个变量SuccessFailureLeaves

       for (i = 1; i <= Tree[now][0]; i++)

       {

              solve(Tree[now][i]);

       }

       for (i = 1; i <= Tree[now][0]; i++)

       {

              Leaves[now] += Leaves[Tree[now][i]];

              if (worm[now] == 0)

              {

                     Failure[now] += (Failure[Tree[now][i]] + 2);

              }

       }

       //部分贪心应用

       int temp[M];      //记录Tree[now]儿子的优先次序

       for (i = 0; i < Tree[now][0]; i++)

       {

              temp[i] = Tree[now][i + 1];

       }

       //在此贪心

       sort(temp, temp + Tree[now][0], cmp);

       for (i = 1, k = 0; i <= Tree[now][0]; i++)

       {

              Success[now] += (k + 1) * Leaves[temp[i - 1]] + Success[temp[i - 1]];

              k += Failure[temp[i - 1]] + 2;

       }

}

 

int main()

{

       //freopen("1.txt", "r", stdin);

       int i, a;

       char c;

       while (scanf("%d", &N) && N != 0)

       {

              memset(Tree, 0, sizeof(Tree));

              for (i = 1; i <= N; i++)

              {

                     worm[i] = 0;

                     scanf("%d %c", &a, &c);

                     if (a != -1)

                     {

                            Tree[a][++Tree[a][0]] = i;

                     }

                     if (c == 'Y')

                     {

                            worm[i] = 1;

                     }

                     Leaves[i] = 0; Failure[i] = 0; Success[i] = 0;

              }

              //树形dp+部分贪心

              solve(1);

              double ans = ((double)Success[1]) / ((double)Leaves[1]);

              printf("%.4lf/n", ans);

       }

 

       return 0;

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值