众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:
勇太有一棵 n 个节点的以1为根的有根树。现在他可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉这个点和它的儿子之间的所有边。
现在勇太想要知道对于每一个 k ∈ [1, n],最少需要多少次操作才能让图中恰好存在 k 个联通块。
当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?
第一行输入一个正整数 n (n ≤ 3000)。
第二行输入 n-1 个整数 fi 表示 i+1 号点的父亲,保证 1 ≤ fi ≤ i。
输出 n 个整数,第 i 个数表示 k=i 时的答案,如果无法让图中恰好存在 k 个联通块,则输出-1。
6 1 2 1 1 2
0 -1 1 1 -1 2
题解:
每减去一个节点,产生的联通块的数量就是该节点的孩子数量。 所以, 实质上是一道单重背包问题的变形。
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; const int MAXN = 3005; int n, root[MAXN], dp[MAXN]; int main(){ freopen("in.txt", "r", stdin); int x, max_x, tmp; scanf("%d", &n); memset(root, 0, sizeof(root)); max_x = 0; for(int i=1; i<n; ++i) { scanf("%d", &x); root[x]++; if(max_x < x){ max_x = x; } } memset(dp, 0, sizeof(dp)); dp[0] = 1; for(int i=max_x; i>=1; --i){ if(root[i] != 0){ for(int j=n; j>=root[i]; --j){ if(dp[j-root[i]] != 0){ if(dp[j] != 0){ dp[j] = min(dp[j], dp[j-root[i]] + 1); } else { dp[j] = dp[j-root[i]] + 1; } } } } } for(int i=0; i<n; ++i){ if(dp[i]){ printf("%d ", dp[i]-1); }else{ printf("%d ", -1 ); } } printf("\n"); return 0; }
hihocoder-1453-Rikka with Tree
原文:http://www.cnblogs.com/zhang-yd/p/6220534.html