题目:http://poj.org/problem?id=1694
容易想到,从树上取下来的石子可以在别的子树上使用,对以X为根的子树进行Mark,假设X有N个孩子,则N个孩子必须都挂上石子之后,X才能被挂上石子,这个对这N个孩子挂石子的过程中,如果第i个孩子没有挂上石子的时候,就试图去挂j,则肯定不会优于先挂完i再去挂j,毕竟同时进行不会要求更少的总石子数,所以我们可以先挂上一个孩子,再去挂另外一个孩子,使得要求的石子数最少。
但问题是,那到底先从哪个孩子下手呢,这N个孩子已什么样的顺序去处理呢?知觉告诉我们,先挂要求总石子数多的,因为这样再去挂少的就甚至可以不用增加了总石子数了。假设一个处理顺序中,这N个孩子挂上石子分别需要总数为a[1]~a[N]的石子,且存在i,有a[i] < a[i+1],并假设在挂i之前需要的一开始的总数为T。
考虑开始挂i之前,我们能复用的前面剩下来的石子数,假设为R,
(1)R > a[i+1],则需要的总数为T,还剩R-2个可以用
(2)a[i] < R <= a[i+1],则需要的总数变为T+a[i+1]-R+1,还剩a[i+1]-1个可以用
(3)R <= a[i],则需要的总数变为T+a[i+1]-R+1,还剩a[i+1]-1个可以用
现在,如果我们反过来先挂a[i+1]再挂a[i],看看会是什么结果:
(1)R > a[i+1],则需要的总数为T,还剩R-2个可以用
(2)a[i] < R <= a[i+1],则需要的总数变为T+a[i+1]-R,还剩a[i+1]-2个可以用
(3)R <= a[i],则需要的总数变为T+a[i+1]-R,还剩a[i+1]-2个可以用
可以看到,先挂a[i],我们可能会在一开始的总数上多需要一个石子,然后这个石子继续在挂下一个子树的时候使用,所以i不能是N-1,否则,多出来的这个石子不会在为挂i+2时起作用,只会让结果变差,所以必须有a[N-1] >= a[N]。
那i可不可以是N-2呢,即会不会有a[N-2] < a[N-1] >= a[N]这样的情况呢,在上面(2)(3)的情况下继续分析:
(1)如果有a[N-1]=a[N],则无论先N-2还是N-1,需要的都是T+a[N-1]-R+2
(2)如果有a[N-1]=a[N]+1,则无论是先N-2还N-1,需要的都是T+a[N-1]-R+1
(3)如果有a[N-1]>=a[N]-2,则先选N-1需要T+a[i+1]-R,而先选N-2需要T+a[i+1]-R+1,先选N-1优于先选N-2
所以我们需要有a[N-2]>=a[N-1}的顺序以保证最优。
类似地分析,我们可以得到a[N-3] < a[N-2] >=a N-1] >= a[N]的顺序不会优于且有可能差于a[N-3] >= a[N-2] >=a[N-1] >= a[N]的顺序,以此类推,最后我们有,任一顺序不会优于且可能差于a[1]>=a[2]>=a[3]>=...>=a[N]的顺序。
所以,在知道孩子子树需要的总数值后,从大到小排序,不够就补,便可以得到根子树需要的最少总数值了。
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAX_N 205
int N;
vector<int> children[MAX_N];
vector<int> childRes[MAX_N];
int mark(int x)
{
if(children[x].empty()) return 1;
vector<int>& c = children[x];
vector<int>& r = childRes[x];
r.resize(c.size());
for(int i = 0; i < c.size(); ++i) r[i] = mark(c[i]);
sort(r.begin(), r.end());
int n = 0, t = 0;
for(int i = r.size() - 1; i > -1; --i){
if(n >= r[i]) --n;
else{
t += r[i] - n;
n = r[i] - 1;
}
}
return t;
}
int main()
{
int test, i, p, cnt, c;
for(scanf("%d", &test); test--; ){
scanf("%d", &N);
for(i = 1; i <= N; ++i) children[i].clear();
for(i = 1; i <= N; ++i){
scanf("%d%d", &p, &cnt);
while(cnt--){
scanf("%d", &c);
children[p].push_back(c);
}
}
printf("%d\n", mark(1));
}
return 0;
}