2022-05-07每日刷题打卡
代码源——每日一题
题目描述
有一棵 n 个节点的以1为根的有根树。现在可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉这个点和它的儿子之间的所有边。
现在想要知道对于每一个 k∈[1,n],最少需要多少次操作才能让图中恰好存在 k 个联通块。
输入格式
第一行输入一个正整数 n。
第二行输入 n−1 个整数 fi 表示 i+1 号点的父亲,保证 1≤fi≤i。
输出格式
输出 n 个整数,第 i 个数表示 k=i 时的答案,如果无法让图中恰好存在 k 个联通块,则输出-1。
样例输入1
6
1 2 1 1 2
样例输出1
0 -1 1 1 -1 2
数据规模
共10个测试点。
测试点1,2,3满足n≤20。
测试点4,5,6满足n≤100。
对于所有数据,满足1≤n≤3000。
问题解析
这题其实就是背包问题的转化。
因为我们是每次选一个点,把它和它孩子的所有点的链接都断开,拿样例来说,连在第一个点上的点有三个,那么我们要是选择了第一个点,那么第一个点和它三个孩子的链接就会断开,这样此时就会有4个连通块:第一个点+第一个点的三个孩子。连在第二个点上的孩子有两个,所以我们要是断开第二个点,那么连通块就会有三个,如果把1和2都选了,那么一共就是6个连通块(样例如图所示)。
那么此时就相当于一个背包问题了,我们转化一下:每个点有多少孩子,相当于这个货物值多少钱。比如第一个点,有3个孩子,所以它的价值是3,当我们选择拿这个点放入背包,那么我们的价值就+3,即多了三个连通块。然后我们就根据需要的总价值(总连通块数)来选择我们需要的货物即可。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int MOD = 1e9 + 7, N = 3010;
int n, f[N], w[N];
int main()
{
cin >> n;
for (int i = 2; i <= n; i++)
{
int x;
cin >> x;
w[x]++;
}
memset(f, 0x3f, sizeof f);
f[1] = 0;
for (int i = 1; i <= n; i++)
{
if (w[i] == 0)continue;
for (int j = n; j >= w[i]; j--)
f[j] = min(f[j], f[j - w[i]] + 1);
}
for (int i = 1; i <= n; i++)
{
if (f[i] == 0x3f3f3f3f)cout << -1 << " ";
else cout << f[i] << " ";
}
return 0;
}