您的位置:首页 > 其它

poj 2057 树形dp+贪心(蜗牛找家的期望值)

2015-09-30 12:09 274 查看
题意:一只小蜗牛爬树的时候从树枝的末端(也就是从叶子结点)上掉了下来,但是它的壳子还留在上面。于是它从根节点去寻找它的壳子(但是它完全忘记了之前走过的路)。在路途中有些节点上可能住着虫子,虫子可以告诉小蜗牛它之前来没来过。假如壳子在每个叶子节点上的概率相等,为蜗牛选出一条路,使得所需要走的路程的期望最小。

思路:树形dp+贪心,完全自己想出来的,开心!!

显然可以构造状态dp[x][0]表示目的地不在以x为根的子树上需要走的路程期望(相当于遍历这个子树走过的路程),dp[x][1]表示从x出发,且目的地在以x为根的子树上走的路程期望。显然如果x上有虫子,那么dp[x][0]=0;否则dp[x][0] = sum(dp[y][0]+2) , y是x的儿子;

那么求dp[x][1]的时候涉及的问题就是儿子的访问顺序,如果考虑全排列,题目给了每个结点最多有8个儿子,那么复杂度是O(8!*n) = O(40,320,000),时限3s,现在想想说不定可以。但是最好的做法当然是找出规律,贪心来选择顺序。

考虑两个连续的儿子i和j,目的地在i所在子树的期望是son(i)*k+dp[i][1](其中k是访问其他儿子没找到经过的路径),目的地在j的期望是son(j)*(k+dp[i][0]+2)+dp[j][1](+2表示从i回到x,再从x到j)。如此再写出j在前i在后的和,两相比较消去相同项得到前者是son(j)*(bi+2),后者是son(i)*(bj+2),那么如果i在前,所要求的条件就是前者小于后者,那么也就是(bi+2)/son(i)要小于(bj+2)/son(j)。这是贪心经典的交换论证方法。也就是说最优的排列应该按照(bi+2)/son(i)的大小来进行排序。

最后,输出dp[1][1]/son[1]即可。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 1001
bool ch
;
int dp
[2],first
,top,son
;
int n;
struct edge{
int y,next;
}e
;
struct node{
int a,b,son;
};
void add(int x,int y){
e[top].y = y;
e[top].next = first[x];
first[x] = top++;
}
int cmp(node x,node y){
return (x.b+2)*y.son < (y.b+2)*x.son;
}
void dfs(int x){
int i,j,k,y;
struct node p
;
j = 0;
dp[x][0] = dp[x][1] = 0;
if(first[x] == -1){
son[x] = 1;
return;
}
son[x] = 0;
for(i = first[x];i!=-1;i=e[i].next){
y = e[i].y;
dfs(y);
dp[x][0] += dp[y][0]+2;
son[x] += son[y];
p[j].a = dp[y][1];
p[j].b = dp[y][0];
p[j++].son = son[y];
}
if(ch[x])
dp[x][0] = 0;
sort(p,p+j,cmp);
for(i = 0,k=1;i<j;i++){
dp[x][1] += p[i].son*k+p[i].a;
k += p[i].b+2;
}
}
int main(){
while(scanf("%d",&n) && n){
int i,j;
char str;
top = 0;
clr(first, -1);
clr(ch, false);
for(i = 1;i<=n;i++){
scanf("%d %c",&j,&str);
if(i>1)
add(j,i);
if(str == 'Y')
ch[i] = true;
}
dfs(1);
printf("%.4lf\n",(double)dp[1][1]/son[1]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: