新年第一题,一道树上的动态规划,树的最大独立集问题。
书中介绍最大独立集时提到了刷表法,却说有一种更实用的方法将在例题中介绍,应该说的就是这题。
按照这种方法的思路,我们不必枚举所有儿子和所有孙子,只需要加一个维度来表示是否选中当前节点然后只枚举儿子。
对于已经选中的节点,在记忆化搜索的递归时,将它所有儿子的这个维度设为“不选中”即可。
对于不选中的节点,则对于每个儿子,分别求出选中/不选中的dp值,然后选取较大者即可。
对于这道题的特殊要求:唯一性,书里用 f=1 表示“唯一”,f=0 表示“不唯一”,其实不太方便。因为对于某一个节点,只有他的全部儿子节点都唯一时,他自己才唯一。所以不如把 f 的用法调转,这样只要碰到不唯一的儿子,这个节点就可以设置成“不唯一”了。
Run Time: 0.035s
#define UVa "LT9-13.1220.cpp" //Party at Hali-Bula
char fileIn[30] = UVa, fileOut[30] = UVa;
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<iostream>
#include<string>
using namespace std;
//Global Variables. Reset upon Each Case!
const int maxn = 200 + 10;
int n;
vector<int> S[maxn];
map<string, int> Names;
int d[maxn][2];
int dup[maxn][2];
/
int dp(int u, int f) {
if(d[u][f]) return d[u][f];
if(S[u].size() == 0) { //leaf node.
d[u][f] = f;
}
else { //internal node.
if(f == 1) { //node selected.
d[u][f] = 1;
for(int i = 0; i < S[u].size(); i ++) {
d[u][f] += dp(S[u][i], 0);
if(dup[S[u][i]][0]) dup[u][f] = 1;
}
}
else { //node not selected.
d[u][f] = 0;
for(int i = 0; i < S[u].size(); i ++) {
int a = dp(S[u][i], 0), b = dp(S[u][i], 1), c = max(a,b);
d[u][f] += c;
if( a == b
|| (a > b && dup[S[u][i]][0])
|| (a < b && dup[S[u][i]][1])
) dup[u][f] = 1;
}
}
}
return d[u][f];
}
void init() {
for(int i = 0; i < maxn; i ++) S[i].clear();
Names.clear();
memset(d, 0, sizeof(d));
memset(dup, 0, sizeof(dup));
string tmp, tmpboss;
int cnt = 0;
cin>>tmp;
Names[tmp] = cnt++;
for(int i = 1; i < n; i ++) {
cin>>tmp>>tmpboss;
int a, b;
if(!Names.count(tmp))
a = Names[tmp] = cnt ++;
else
a = Names[tmp];
if(!Names.count(tmpboss))
b = Names[tmpboss] = cnt ++;
else
b = Names[tmpboss];
S[b].push_back(a);
}
}
int main() {
while(scanf("%d", &n) && n) {
init();
int a = dp(0,0), b = dp(0,1);
if(a == b)
dup[0][0] = dup[0][1] = 1;
if(a > b)
printf("%d %s\n", a, (dup[0][0])?"No":"Yes");
else
printf("%d %s\n", b, (dup[0][1])?"No":"Yes");
}
return 0;
}