pku 2057 The Lost House 解题报告
一、题目大意:
蜗牛的房子遗失在了一棵树的某个叶子结点上,它要从根结点出发开始寻找它的房子。有一些中间结点可能会住着一些虫子,这些虫子会告诉蜗牛它的房子是否在以这个中间结点为根的子树上,这样蜗牛就不用白跑路了。当然,如果有些结点没有住着虫子的话,那么可怜的蜗牛只有靠自己决定访问顺序来探索了。假设蜗牛走过一条边的耗费都是1,且房子遗失在每个叶子结点的概率都是相等的,那么请问蜗牛找到他的房子的最小数学期望值?
我们约定,树上的结点数n最多为1000,每个结点的分叉数k最多为8。
例如在sample的这棵树当中:
蜗牛从根结点1出发开始寻找它的房子,它的房子可能遗失在2、4、5。在结点3上住着一只虫子,它会告诉蜗牛,以3为根的子树上是否有蜗牛的房子。蜗牛有两种走法。蜗牛可以先访问2,如果它在那儿不能找到房子,那么它要回到根结点1,再通过3来访问结点4(或5),如果还是不能找到它的房子,那么它又要回到结点3,再去访问结点5(或4)。在这种走法中,当房子分别位于2、4、5的时候,蜗牛需要走的步数分别是1、4、6,期望值是(1+4+6)/3=11/3。显然,这种走法没有充分发挥虫子在这里起到的作用。在另一种走法中,蜗牛先访问结点3,它可以从住在3上的虫子那里得知它的房子是否存在于4或5的信息。在这种走法中,当房子分别位于2、4、5的时候,蜗牛需要走的步数分别是3、2、4,期望值是(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个变量Success、Failure、Leaves
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;
}