题目大意
多组输入,当N为0时输入终止。
每组数据的第一行为最大的Boss,第2-n行中每一行包含两个字符串,代表员工A和B,且A的直接上级为B。当A参加聚会时,A的直接上级和直接下级都不能参加。求出能够参加聚会的最大人数并说明是否存在多组解。
解题思路
经典的树形DP题目,难点并不在求解最大值的过程中,而在判断多解的过程中。
由于题目中的输入为字符串,所以可以使用map使字符串和数字形成一对一的映射,便于处理。
转移过程:
主体遍历过程是DFS的过程,当某个点未被访问且为当前根节点的下属时,递归的调用该函数,将该点作为根节点处理,并在回溯的过程中进行状态转移
转移方程:
dp[now][1]+=dp[i][0];
dp[now][0]+=max(dp[i][1],dp[i][0]);
主要思路就是当选择当前根节点时,其子节点均不能选择,当不选择当前根节点时,则在是否选择子节点中选择具有最大值的情况。
判断多解:
我们先来分析一下多解产生的必要条件:
当有多解的情况产生时,一定有一个点满足dp[i][1]==dp[i][0],由于该状态是由子节点产生的,所以无需再考虑其子节点的情况,只要考虑父节点就好,其父节点一定满足dp[j][0]>dp[j][1]。即不去的时候更优。很容易理解,若父节点去了则该节点一定不去,这样就相当于在两种解中选了特定的一种,而只有父节点不去的时候该点的两种方案结果相同,才满足多解的条件。当该节点为总根节点的时候则无需考虑其父节点的情况,直接满足多解的条件。
附上代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
using namespace std;
int n,boss[1005],vis[1005],dp[1005][2],mmax=0;
bool flag=true;
void solve(int now)
{
vis[now]=1;
dp[now][1]=1;
for(int i=1;i<=n;i++){
if(boss[i]==now&&!vis[i]){
solve(i);
dp[now][1]+=dp[i][0];
dp[now][0]+=max(dp[i][0],dp[i][1]);
}
}
}
int main()
{
while(~scanf("%d",&n),n!=0)
{
mmax=0;
flag=true;
map<string,int> compare;
char root[1005],peo1[1005],peo2[1005];
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
memset(boss,0,sizeof(boss));
int cont=2;boss[1]=1;
scanf("%s",root);
compare[root]=1;
for(int i=1;i<n;i++){
scanf("%s %s",peo1,peo2);
if(!compare.count(peo1))
compare[peo1]=cont++;
if(!compare.count(peo2))
compare[peo2]=cont++;
boss[compare[peo1]]=compare[peo2];
}
solve(1);
mmax=max(dp[1][0],dp[1][1]);
for(int i=1;i<=n;i++){
if(dp[i][0]>dp[i][1])
for(int j=1;j<=n;j++){
if(boss[j]==i&&dp[j][0]==dp[j][1])
{
flag=false;break;
}
}
if(!flag)break;
}
if(flag&&dp[1][0]!=dp[1][1])
printf("%d Yes\n",mmax);
else
printf("%d No\n",mmax);
}
return 0;
}