代码源 #154. 树
题目描述
有一棵 nn 个节点的以11为根的有根树。现在可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉这个点和它的儿子之间的所有边。
现在想要知道对于每一个 k∈[1,n]k∈[1,n],最少需要多少次操作才能让图中恰好存在 kk 个联通块。
输入格式
第一行输入一个正整数 nn。
第二行输入 n−1n−1 个整数 fifi 表示 i+1i+1 号点的父亲,保证 1≤fi≤i1≤fi≤i。
输出格式
输出 nn 个整数,第 ii 个数表示 k=ik=i 时的答案,如果无法让图中恰好存在 kk 个联通块,则输出-1。
样例输入1
6
1 2 1 1 2样例输出1
0 -1 1 1 -1 2样例输入输出2
见下发文件。
数据规模
共10个测试点。
测试点1,2,31,2,3满足n≤20n≤20。
测试点4,5,64,5,6满足n≤100n≤100。
对于所有数据,满足1≤n≤30001≤n≤3000。
题目大意:
对于一颗树,每次可以删除一个节点和子节点的边,问最少删除多少个节点的边可以得到k个连通块,无法得到就输出-1。
思路:
不难发现,每删去一条边,会就多一个连通块出来,所以题目就转化为了选最少的节点删除得到对应的数量的连通块。我们可以用一个w[i]数组存储每个节点它拥有的子节点的数量,w[i]=k就相当于i号节点有k个子节点。这时侯就相当于一个01背包问题了,每个节点都有一个价值w,重量都为1,对于每个节点都有选或不选两种情况。其中要注意的是一般01背包取最多物品,这里则是取最少物品,状态转移方程为dp[j] = min(dp[j], dp[j - w[i]] + 1)。
AC代码(带注释):
1 #include<algorithm> 2 #include<iomanip> 3 #include<stdio.h> 4 #include<cstdio> 5 #include<math.h> 6 #include<iostream> 7 #include<cstring> 8 #include<stack> 9 #include<queue> 10 #include<string.h> 11 #include<set> 12 #include<map> 13 14 using namespace std; 15 #define endl '\n' 16 #define ull unsigned long long 17 #define ll long long 18 #define ld long double 19 #define PII pair<int,int> 20 const int N = 3007; 21 const int mod = 1e9+7; 22 23 int n, dp[N], w[N]; 24 25 int main() 26 { 27 //要是超时可能就是没加上这句话 28 ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); 29 30 cin >> n; 31 for (int i = 2; i <= n; i++) 32 { 33 int x; 34 cin >> x; 35 w[x]++;//记录i号节点的子节点个数 36 } 37 for (int i = 0; i < N; i++) 38 { 39 dp[i] = N; 40 } 41 dp[1] = 0;//初始化 42 for (int i = 1; i <= n; i++) 43 { 44 if (w[i] == 0)continue;//如果i号节点没有子节点,跳过 45 for (int j = n; j >= w[i]; j--) 46 { 47 dp[j] = min(dp[j], dp[j - w[i]] + 1);//状态转移方程 48 } 49 50 } 51 52 for (int i = 1; i <= n; i++) 53 { 54 if (dp[i] == N) 55 { 56 cout << -1 << " "; continue; 57 } 58 cout << dp[i] << " "; 59 } 60 return 0; 61 }
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号