[Daimayuan] 树(C++,动态规划,01背包方案数)

有一棵 n n n 个节点的以 1 1 1 号点为根的有根树。现在可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉连接这个点和它的儿子的所有边。

现在我们想知道对于每一个 k k k ( 1 ≤ k ≤ n 1≤k≤n 1kn),最少需要多少次操作能让图中恰好存在 k k k 个联通块。

输入格式

第一行输入一个正整数 n n n

第二行输入 n − 1 n−1 n1 个整数 f 1 , f 2 , . . . , f n − 1 f_1,f_2,...,f_{n−1} f1,f2,...,fn1 f i f_i fi 表示 i + 1 i+1 i+1 号点的父亲,保证 1 ≤ f i ≤ i 1≤f_i≤i 1fii

输出格式

输出 n n n 个整数,第 i i i 个数表示 k = i k=i k=i 时的答案,如果无法让图中恰好存在 i i i 个联通块,则输出 -1

样例输入1
6
1 2 1 1 2
样例输出1
0 -1 1 1 -1 2
数据规模

10 10 10 个测试点。

测试点 1 , 2 , 3 1,2,3 1,2,3 满足 n ≤ 20 n≤20 n20

测试点 4 , 5 , 6 4,5,6 4,5,6 满足 n ≤ 100 n≤100 n100

对于所有数据,满足 1 ≤ n ≤ 3000 1≤n≤3000 1n3000

解题思路

对于一棵树来说,删去任意一条边都会使连通块数目 + 1 +1 +1

那么要判断能否得到 k k k个连通块,我们只需要判断能否恰好删去 k − 1 k-1 k1条边。

题目要求操作为:删除一个节点与子节点之间的所有边。

那么统计每个节点的子节点数目,然后就变为了01背包可行性问题:

每一个节点都是一个物品,问能否恰好装满容量为 k − 1 k-1 k1的背包?

for (int i = 1; i <= n; i++) {//尝试每一个物品
	for (int j = 0; j < n; j++) {//尝试新的重量组合
		if (j - items[i] >= 0)
        	ans[i][j] = ans[i - 1][j] || ans[i - 1][j - items[i]];
    	else
            ans[i][j] = ans[i - 1][j];
    }
}

以上我们只是检验了可行性问题,但是题目中还有另外一个要求:操作次数最少。

因为在物品组合中没有先后顺序,所以我们可以通过物品组合中的物品数量来确定操作次数。

只有新的操作次数小于旧的操作次数的时候,我们才进行更新。

for (int i = 1; i <= n; i++) {//尝试每一个物品
	for (int j = 0; j < n; j++) {//尝试新的重量组合
        if (j - items[i] >= 0 && ans[i - 1][j] && ans[i - 1][j - items[i]])
        	ans[i][j] = min(ans[i - 1][j], ans[i - 1][j - items[i]] + 1);
    	else if (ans[i - 1][j])
            ans[i][j] = ans[i - 1][j];
        else if (j - items[i] >= 0 && ans[i - 1][j - items[i]])
            ans[i][j] = ans[i - 1][j - items[i]];
    }
}

注:1)以上代码段中,ans中元素的含义发生了变化:可行/不可行 -> 物品数量;

2)为了与不存在的组合(ans[i][j] = 0)相区分,我们为所有存在的组合物品数量添加偏置bias,也就是说,物品数量 = ans[i][j] - bias

以上代码的空间复杂度、时间复杂度均可以接受,可以AC,接下来是优化部分qwq。

因为嫌弃这个算法的空间复杂度,所以我们对其进行优化,压缩到二维数组:

for (int i = 1; i <= n; i++) {//尝试每一个物品
	for (int j = 0; j < n; j++) {//尝试新的重量组合
        if (j - items[i] >= 0 && ans[(i - 1) % 2][j] && ans[(i - 1) % 2][j - items[i]])
        	ans[i % 2][j] = min(ans[(i - 1) % 2][j], ans[(i - 1) % 2][j - items[i]] + 1);
    	else if (ans[(i - 1) % 2][j])
            ans[i % 2][j] = ans[(i - 1) % 2][j];
        else if (j - items[i] >= 0 && ans[(i - 1) % 2][j - items[i]])
            ans[i % 2][j] = ans[(i - 1) % 2][j - items[i]];
    }
}

还是嫌弃?继续压,压缩到一维数组:

for (int i = 1; i <= n; i++) {//尝试每一个物品
	for (int j = n; j >= items[i]; j--) {//尝试新的重量组合
		if (j - items[i] >= 0 && ans[j] && ans[j - items[i]])
            ans[j] = min(ans[j], ans[j - items[i]] + 1);
		else if (ans[j]) ans[j] = ans[j];
        else if (j - items[i] >= 0 && ans[j - items[i]])
            ans[j] = ans[j - items[i]] + 1;
	}
}

然后我们删除一些无用的部分:

for (int i = 1; i <= n; i++) {//尝试每一个物品
	for (int j = n; j >= items[i]; j--) {//尝试新的重量组合
		if (ans[j] && ans[j - items[i]]) ans[j] = min(ans[j], ans[j - items[i]] + 1);
		else if (ans[j - items[i]]) ans[j] = ans[j - items[i]] + 1;
	}
}

嗯,好看多了qwq。

最后,AC代码如下:

#include <iostream>
using namespace std;
const int max_n = 3000;

int ans[max_n + 1], n;
int items[max_n + 1];


int main() {
	cin >> n;
	int fa;
	for (int i = 1; i < n; i++) {
		cin >> fa;
		items[fa]++;
	}

	int bias = 1;
	ans[0] = bias;
	for (int i = 1; i <= n; i++) {//尝试每一个物品
		for (int j = n; j >= items[i]; j--) {//尝试新的重量组合
			if (ans[j] && ans[j - items[i]]) ans[j] = min(ans[j], ans[j - items[i]] + 1);
			else if (ans[j - items[i]]) ans[j] = ans[j - items[i]] + 1;
		}
	}

	cout << 0;
	for (int i = 2; i <= n; i++) {
		cout << ' ' << ans[i - 1] - bias;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值