The Lost House POJ - 2057(树形dp+贪心 (双线最优子结构问题))

思路

题意:有一只蜗牛爬上树睡着之后从树上掉下来,发现后面的”房子”却丢在了树上面, 现在这
只蜗牛要求寻找它的房子,它又得从树根开始爬起,现在要求一条路径使得其找到房子
所要爬行的期望距离最小. 爬行距离如下计算, 题目规定每一个分支和枝末都看做是
一个节点, 这些节点之间的距离都是1, 在分支上可能会有热心的毛毛虫, 这些毛毛虫
会如实的告诉蜗牛他之前是否经过这条路径, 也正是因为毛毛虫, 因此询问毛毛虫的顺
序使得这题的期望是不同的. 输入数据时给定的一个邻接关系,通过上一个节点来构图
同时字符 ‘Y’表示该点有毛毛虫, 字符’N’表示该点

解法:dp[i][0]表示以该点为根的节点找不到房子时要爬行最少的距离, 这个值有什么用呢?
这个值就是用去计算房子落在其他叶子节点时,其对爬行距离的负面效应有多大.
dp[i][1]表示以该点为根的节点在选择好所有分支点的爬行方案后,枚举完房子落在该
子树所有叶子节点上的总爬行距离的最小值,这是一个值告诉我们这棵子树对爬行距离
的正面效应有多大.那么有动态方程: ch[x]表示x节点一共有多少个孩子节点

 dp[i][0] = sum{ dp[j][0] + 2 } 当i没有毛毛虫且要求j是i的孩子节点,这个是很好
 理解的, 多出来的2就是连接该孩子节点的边来回的两次 
 dp[i][0] = 0 当该点有毛毛虫的时候, 原因是因为毛毛虫会告诉我们这点下面没有房子
 当一个节点时叶子节点的时候,那么 dp[i][0] = dp[i][1] = 0; 

 要明确dp[i][1]是表示在遇到分支选择的先后顺序决定后,我们枚举房子在其各个叶子
 上的所要爬行的总距离
 dp[i][1] = sum{ (sum{dp[1..j-1][0]+2}+1}*ch[j] +  dp[j][1]}, 其中j是i的孩子 
 这个方程要怎么去理解呢? 意思翻译过来就是遍历i的孩子中的j号子树所有叶子节点所
 要爬行的最短距离.其值就是: 
 前面[1, j-1]号子树没有找到节点所爬行的最短距离加上走了的多余的边再加上遍历j
 号子树所有叶子节点所爬行的最短距离, 那么这里显然谁前谁后就会决定最后值的大小

 现在只考虑序列中任意两棵子树A,B, 如果A放前面的话, 枚举完所有房子所在位置后的总距离就是
 ans1 = (ch[A] + dp[A][1]) + ((dp[A][0]+2)*ch[B] + dp[B][1] + ch[B])
 前一个括号是假设枚举房子落在A的所有叶子上, 后面的扩后是枚举在B的所有叶子上 
 ans2 = (ch[B] + dp[B][1]) + ((dp[B][0]+2)*ch[A] + dp[A][1] + ch[A])

 ans1 - ans2 = (dp[A][0]+2)*ch[B] - (dp[B][0]+2)*ch[A]

我的理解

:树形dp的思路,求出走每颗子树的最小步数。因为对于每颗子树,要么在这里找到壳,要么没有找到壳,所以设置dp[i][0]表示从i开始,遍历完所有子树,不能找到壳的最少步数,dp[i][1]表示,在最优策略下找到壳的最少步数(这和我们普通的dp不同,他是由两个最优子结构决定的,我们设的dp转移是一个最优子结构,而dp[i][1]表示的最优策略也是一个最优子结构。我们发现其实在最优策略下找到壳是可以贪心的,因为必然是朝失败次数少的方向转移)。
对于这样的双线最优子结构问题,常见的思路都是通过贪心来优化掉其中一线,否则复杂度会爆。就这题来说,如果不贪心优化的话,对于dp[i][1]就需要状压来求(二进制枚举顺序).

代码

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
const int maxn=1e3+8;
typedef long long ll;
struct Edge
{
    int u,v,w,next;
}edge[maxn];
int num,n;
int head[maxn];
int dp[maxn][2];
int vis[maxn];
int ch[maxn];
void addEdge(int u,int v,int w)
{
    edge[num].u=u;
    edge[num].v=v;
    edge[num].w=w;
    edge[num].next=head[u];
    head[u]=num++;
}
void init()
{
    memset(head,-1,sizeof(head));
    memset(dp,0,sizeof(dp));
    memset(ch,0,sizeof(ch));
    memset(vis,0,sizeof(vis));
    num=0;
}
bool cmp(int a,int b)
{
    return (dp[a][0]+2)*ch[b]<(dp[b][0]+2)*ch[a];
}
int dfs(int x)
{
    if(head[x]==-1)
    {
        dp[x][0]=dp[x][1]=0;
        return ch[x]=1;
    }
    vector<int> g;
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        ch[x]+=dfs(v);
        g.push_back(v);
    }
    sort(g.begin(),g.end(),cmp);
    for(int i=0;i<g.size();i++)
    {
        dp[x][1]+=dp[x][0]*ch[g[i]]+dp[g[i]][1]+ch[g[i]];
        //前面失败的+g[i]次成功的
        dp[x][0]+=dp[g[i]][0]+2;//找不到必然是子树找不到+来回两次跑
    }
    if(vis[x]) dp[x][0]=0;
    return ch[x];
}
int main(int argc, char const *argv[])
{
    while(scanf("%d",&n)!=EOF&&n)
    {
        char s[5];
        int p;
        scanf("%d %s",&p,s);
        init();
        for(int i=2;i<=n;i++)
        {
            scanf("%d %s",&p,s);
            vis[i]=(s[0]=='Y');
            addEdge(p,i,0);
        }
        dfs(1);
        printf("%.4f\n",1.0*dp[1][1]/ch[1]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40774175/article/details/82670317