hdu2412Party at Hali-Bula【树型DP入门题】

因为C#大作业搁置了5天的Dp啊T^T关键是大作业也是草草收尾的,这是一个忧伤的故事(@﹏@)~从九点到现在就改明白一个树型DP,这让故事变的更加忧伤了==

什么是树型动态规划:
树本身就是一个递归的结构,所以在树上进行动态规划或者递推是在合适不过的事情。
必要条件:子树之间不可以相互干扰,如果本来是相互干扰的,那么我们必须添加变量使得他们不相互干扰。

所以说,树型DP他很像记忆化搜索,都是递归的动态规划,伦家好喜欢( *︾▽︾)

先说那个基础的树型dp入门题

聚会的快乐(oi的题貌似)

你要组织一个由你公司的人参加的聚会。你希望聚会非常愉快,尽可能多地找些有趣的热闹。但是劝你不要同时邀请某个人和他的上司,因为这可能带来争吵。给定N个人(姓名,他幽默的系数,以及他上司的名字),编程找到能使幽默系数和最大的若干个人。

题解:

      我们可以很轻松的在程序构建出上面那棵树,不管是链表还是邻接矩阵。
      这时我们可以分析一下这棵树
      1.  一个节点选不选只会影响到它的子树或者他的父节点
      2.  一个节点只有取或不取这两种状态
      3.  只有根节点和叶子节点比较特殊,只与起子节点或父节点相关,可以用来做递推的开始或结尾,当然结尾就是答案,为了降低算法的复杂度,用叶子开始推,根节点作结尾会简单许多。

既然已经确定了递推的顺序,我们就设计一下状态表示
假设有这样一个数字f[i][0~1]
f[i][0]表示i这个节点不取,而它的子树都满足条件的情况的最大值
f[i][1]表示i这个节点取,而它的子树都满足条件的情况的最大值

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int mm=222;
int head[mm],next[mm],ver[mm];
/**head[i]表示第i个人指向的链表头*/
/**ver[i]表示链表中的数值*/
/**next[i]表示链表元素的下一个元素*/
int f[mm][2];
/**f[i][0]表示不选这个员工时,它和它整个下属的最大幽默系数*/
/**f[i][1]表示选这个员工时,它和它整个下属的最大幽默系数*/
int w[mm];/**w[i]表示第i个人的幽默指数*/
bool c[mm];/**c[i]标记第i个人是否有老板*/
char name[mm][22],s1[22],s2[22];/**name[i]表示第i个人的名字,s1,s2用来读入名字*/
int n,size,edge;/**n表示多少条读入,size表示已编号人数,edge表示关系边数*/
void addedge(int u,int v)/**建立关系边,这个是用数组模拟指针链表,效率会高一点,也好写一点*/
{
    ver[edge]=v,next[edge]=head[u],head[u]=edge++;
    /**给u的链表增加元素,具体自己手动试一下就明白了*/
}
int getID(char a[])/**给名字编号*/
{
    for(int i=1;i<=size;++i)/**查看是否编过好号,编过直接退出编号*/
        if(strcmp(a,name[i])==0)return i;
    strcpy(name[++size],a);/**给它编个号*/
    return size;/**返回编号*/
}
void treeDP(int u)/**树型DP,一般都是以搜索的形式来做*/
{
    f[u][0]=0;/**没选这个人可能幽默值是0*/
    f[u][1]=w[u];/**选这个人幽默值至少为他自己*/
    for(int i=head[u],v/**i等价于指针*/;i>=0;i=next[i])/**枚举u的链表,也就是他的下属*/
    {
        treeDP(v=ver[i]);/**先取得下属的f[v][0],f[v][1]的值,以便递推*/
        f[u][0]+=max(f[v][0],f[v][1]);/**如果u不选的话,他的下属可以选也可以不选*/
        f[u][1]+=f[v][0];/**如果u选了,它的下属不能选*/
    }
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    int i,v;
    while(scanf("%d",&n)!=-1)
    {
        edge=size=0;/**一开始没有编号*/
        memset(c,0,sizeof(c));/**每个人一开始都不知道有没有老板*/
        memset(head,-1,sizeof(head));/**头指针赋值为-1表示空*/
        while(n--)/**读入员工信息*/
        {
            scanf("%s %d %s",s1,&v,s2);
            w[getID(s1)]=v;/**给这个员工编号,并记住它的幽默指数*/
            c[getID(s1)]=1;/**这个员工有老板*/
            addedge(getID(s2),getID(s1));/**给他的老板编号,并增加关系边*/
        }
        /**虚拟一个公司老总,是所有没有老板的员工的老板*/
        w[0]=0;/**它的幽默指数为0,不不干扰答案*/
        for(i=1;i<=size;++i)
            if(!c[i])addedge(0,i);/**找到没老板的员工,建立关系边*/
        treeDP(0);/**树型DP*/
        printf("%d\n",f[0][0]);/**最终老总不能选,输出不选的最大值就是答案*/
    }
    return 0;
}

写的都这么详细了,那就自以为是的做Party at Hali-Bula,只不过多了一个问说最优解是否唯一罢了(*゚ー゚)

新加一个状态dup[i][j],表示相应的dp[i][j]是否是唯一方案。
对于叶子结点, dup[k][0] = dup[k][1] = 1.
对于非叶子结点,
对于i的任一儿子j,若(dp[j][0] > dp[j][1] 且 dup[j][0] == 0) 或 (dp[j][0] < dp[j][1] 且 dup[j][1] == 0) 或 (dp[j][0] == dp[j][1]),则dup[i][0] = 0
对于i的任一儿子j有dup[j][0] = 0, 则dup[i][1] = 0


开始写吧,各种卡啊,果然是几天不做题手生了(ToT)/~~~ 都卡哪里了

1.之前那个题是每个人有幽默值,问幽默值的最大值,这次的题问能去的最大人数

这个点不取 即dp[i][0]依旧是子节点max(dp[j][1],dp[j][0])之和

而dp[i][1] = 1 + ∑dp[j][0] (j是i的儿子)  那个1什么时候加进去呢(・-・*) 当然是最开始赋值的时候加进去喽

2.dup什么时候算,开始想的是遍历完整个for循环再算,简直扯淡好么,那会哪有子节点了|(*′口`);然后大脑短路觉得可能是再写个函数算,笨死你得了

3.算例子的时候得出两个都是no  ,一段一段换成标称代码试,居然是 else printf("%d No\n",max(dp[1][1],dp[1][0]));笔误了

/**********
hdu2412
2015.12.27
15MS 1756K 2088B C++
**********/
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define N 205
char map[N][105],tmp1[105],tmp2[105],str1[105],str2[105];
struct node{
    int from,to,next;
}edge[2*N];
int head[N],tol,visit[N],dp[N][2],dup[N][2];
void add(int a,int b)
{
    edge[tol].from=a;edge[tol].to=b;edge[tol].next=head[a];head[a]=tol++;
}
int max(int a,int b)
{
    return a>b?a:b;
}
void dfs(int root)
{
    int j,u;
    dp[root][0]=0;
    dp[root][1]=1;
    dup[root][0]=1;
    dup[root][1]=1;
    for(j=head[root];j!=-1;j=edge[j].next)
    {
        u=edge[j].to;
        dfs(u);
        dp[root][0]+=max(dp[u][0],dp[u][1]);
        dp[root][1]+=dp[u][0];
        if(dp[u][0]>dp[u][1] && dp[u][0]==0) dup[root][0]=0;
        else if(dp[u][1]>dp[u][0] && dp[u][1]==0) dup[root][0]=0;
        else if(dp[u][0]==dp[u][1]) dup[root][0]=0;
        if(dup[u][0]==0) dup[root][1]=0;
    }
}
int main()
{
    //freopen("cin.txt","r",stdin);
    int n;
    while(~scanf("%d",&n)&&n)
    {
        scanf("%s",map[1]);
        tol=0;
        int k=1,ans1,ans2,j;
        memset(head,-1,sizeof(head));
        for(int i=1;i<n;i++)
        {
             scanf("%s%s",str1,str2);
            ans1=ans2=-1;
            for(j=1;j<=k;j++)
            {
                if(strcmp(str1,map[j])==0) ans1=j;
                if(strcmp(str2,map[j])==0) ans2=j;
            }
            if(ans1==-1)
            {
                k++;
                ans1=k;
                strcpy(map[k],str1);
            }
            if(ans2==-1)
            {
                k++;
                ans2=k;
                strcpy(map[k],str2);
            }
            add(ans2,ans1);
        }
        dfs(1);
       // for(int i=1;i<=k;i++) printf("%s ",map[i]);
        if(dp[1][0]>dp[1][1]&&dup[1][0]==1) printf("%d Yes\n",dp[1][0]);
        else if(dp[1][1]>dp[1][0]&&dup[1][1]==1) printf("%d Yes\n",dp[1][1]);
        else printf("%d No\n",max(dp[1][1],dp[1][0]));
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值