因为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;
}