树形动态规划问题

动规笔记第三弹

目录

统计人数​

dfs

bfs

没有上司的舞会

dfs

bfs

新的背包

 没有上司的舞会2


统计人数

 dfs:

用vector存入每个人的下属,用size数组表示每个人的下属数量,那么一个人的下属数量等于他的每个直接下属的数量,直接dfs即可。

int a[100001], size_[10001];
vector<int> sub[100001];
void solve(int i) {
	size_[i] = 1;
	for (auto it : sub[i]) {
		solve(it);
		size_[i] += size_[it];
	}
}
int main() {
	int n;
	cin >> n;
	
	for (int i = 2; i <= n; i++) {
		cin >> a[i];
		sub[a[i]].push_back(i);
	}
	solve(1);
	for (int i = 1; i <= n; i++) cout << size_[i] << " ";
	return 0;
}

bfs:

用vector存入每个人的下属,用size数组表示每个人的下属数量,那么一个人的下属数量等于他的每个直接下属的数量

在计算每一个人的下属数量时,我们要预先知道他的直接下属的团队数量,所以要先从 CEO 出发做一遍 bfs,用c数组存放bfs 序,他一定比他的直接下属先被 bfs 到,在上面的例子中,一个合法的 bfs 序为 1 2 5 3 4,我们可以按照4 3 5 2 1 的顺序计算每个员工的团队人数。

int a[100001], size_[10001];
vector<int> sub[100001];
int main() {
	int n;
	cin >> n;
	int c[100001];//存放bfs序
	for (int i = 2; i <= n; i++) {
		cin >> a[i];
		sub[a[i]].push_back(i);
	}
	c[1] = 1;
	for (int k = 1, l = 1; l <= k; l++) {
		int m = c[l];
		for (auto it : sub[m]) {
			c[++k] = it;
		}
	}
	for (int i = n; i; i--) {
		int m = c[i];
		size_[m] = 1;
		for (auto it : sub[m]) {
			size_[m] += size_[it];
		}
	}
	for (int i = 1; i <= n; i++) cout << size_[i] << " ";
	return 0;
}

没有上司的舞会

dfs:

我们只关心考虑 i 号员工的团队,他/她不参加/参加的情况下快乐值最大是几,不关心团队中具体哪些员工参加了舞会

如果i号员工参加了舞会,那么他的直接下属不参加;如果i号员工不参加舞会,那么他的直接下属可以参加也可以不参加

定义 f[i][0] 表示考虑 i 号员工的团队,他不参加的情况下的最大快乐值,定义 f[i][1] 表示考虑 i 号员工的团队,他参加的情况下的最大快乐值;

可得状态转移方程:

f[i][0]= \sum_{j\epsilon sub[i]}^{}max(f[j][0],f[j][1])

f[i][1]=\sum_{j\epsilon sub[i]}^{}f[j][0]+a[i]

ll f[100001][2];
int a[100001];
vector<int> sub[100001];
void solve(int i) {
	f[i][1] = a[i];
	for (auto it : sub[i]) {
		solve(it);
		f[i][0] += max(f[it][1], f[it][0]);
		f[i][1] += f[it][0];
	}
}
int main() {
	int n;
	cin >> n;
	for (int i = 2; i <= n; i++) {
		int x; cin >> x;
		sub[x].push_back(i);
	}
	for (int i = 1; i <= n; i++) cin >> a[i];
	solve(1);
	cout << max(f[1][0], f[1][1]) << endl;
	return 0;
}

bfs:

状态转移方程与dfs相同,在计算i号员工参加或者不参加时,我们要预先知道i号员工的下属的f[j][0]和f[j][1],因此先从 CEO 出发做一遍 bfs,用c数组存放bfs 序,他一定比他的直接下属先被 bfs 到,根据得到的bfs序直接倒着转移一遍

ll f[100001][2];
int a[100001], c[100001];
vector<int> sub[100001];
int main() {
	int n;
	cin >> n;
	for (int i = 2; i <= n; i++) {
		int x; cin >> x;
		sub[x].push_back(i);
	}
	for (int i = 1; i <= n; i++) cin >> a[i];
	c[1] = 1;
	for (int k = 1, l = 1; l <= k; l++) {
		int m = c[l];
		for (auto it : sub[m]) {
			c[++k] = it;
		}
	}
	for (int i = n; i; i--) {
		int m = c[i];
		f[m][1] = a[m];
		for (auto it : sub[m]) {
			f[m][0] += max(f[it][1], f[it][0]);
			f[m][1] += f[it][0];
		}
	}
	cout << max(f[1][0], f[1][1]) << endl;
	return 0;
}

新的背包

 

 用 f[i][j] 表示考虑了前 i 种物品,总体积不超过 j 时的最大收益;

为了计算 f[i][j] 的值,我们枚举第 i 种物品取了 k 个,从中选择收益最大的方案,有:

f[i][j]=f[i-1][j-k]+w[i][k]

int f[510][510], w[510][510];
int n, m;
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> w[i][j];
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 0; k <= j; k++) {
				f[i][j] = max(f[i][j], f[i-1][j - k] + w[i][k] );
			}
		}
	}
	cout << f[n][m];
	return 0;
}

可以类比01背包压缩成一维数组:

int f[510], w[510][510];
int n, m;
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> w[i][j];
	for (int i = 1; i <= n; i++) {
		for (int j = m; j >=1; j--) {
			for (int k = 0; k <= j; k++) {
				f[j] = max(f[j], f[j - k] + w[i][k]);
			}
		}
	}
	cout << f[m];
	return 0;
}

 没有上司的舞会2

 与没有上司的舞会相比多了人数限制,因此数组再加一维,

如果i号员工参加了舞会,那么他的直接下属不参加;如果i号员工不参加舞会,那么他的直接下属可以参加也可以不参加

用 f[i][k][0] 表示考虑 i 号员工的团队,他的团队中一共有不超过 k 个人参加,他不参加的情况下的最大快乐值;用 f[i][k][1] 表示考虑 i 号员工的团队,他的团队中一共有不超过 k 个人参加,他参加的情况下的最大快乐值;

现在我们相应的也要考虑 k —— 团队中参加舞会的人数

当 i 固定的时候,对于所有的 k,我们先来考虑如何计算 f[i][k][1],也就是 i 号员工参加舞会的情况。考虑 i 号员工的某个直接下属 j 号员工,由于 i 号员工参加了,所以 j 号员工不会参加。j 号员工的团队中有不超过 1 人参加时的最大快乐值是 f[j][1][0],有不超过 2 人参加时的最大快乐值是 f[j][2][0],....,有不超过 l 人参加时的最大快乐值是 f[j][l][0]。这是不是和“新的背包”问题很像?

我们把 i 的每个直接下属 j 抽象成“新的背包”问题中的一种物品,把 j 号员工的团队中有不超过 l 人参加抽象成取了 l 个这种物品,可以获得的收益是 f[j][l][0]。就可以用“新的背包”问题的思路解决这个问题啦!现在的人数限制就是“新的背包”问题中的容量限制。

然后,对于所有的 k,我们再来考虑如何计算 f[i][k][0],也就是 i 号员工不参加舞会的情况。这种情况和 i 号员工参加舞会的情况是类似的。考虑 i 号员工的某个直接下属 j 号员工,他既可以参加也可以不参加。我们也可以把 i 的每个直接下属 j 抽象成“新的背包”问题中的一种物品,把 j 号员工的团队中有不超过 l 人参加抽象成取了 l 个这种物品,可以获得的收益是 max(f[j][l][0], f[j][l][1]),也就是在 j 号员工参加/不参加两种情况中选择快乐值较大的一个就可以啦!

ll f[510][510][2];
int a[510];
vector<int> sub[510];
int n, m;
void solve(int i) {
	for (auto it : sub[i]) {
		solve(it);
		for (int j = m; j >= 0; j--) {
			for (int k = 0; k <= j; k++) {
				f[i][j][0] = max(f[i][j][0], f[i][j - k][0] + max(f[it][k][0], f[it][k][1]));
				f[i][j][1] = max(f[i][j][1], f[i][j - k][1] + f[it][k][0]);
			}
		}
	}
	for (int j = m; j; j--) f[i][j][1] = f[i][j - 1][1] + a[i];
	f[i][0][1] = 0;
}
int main() {
	cin >> n >> m;
	for (int i = 2; i <= n; i++) {
		int x; cin >> x;
		sub[x].push_back(i);
	}
	for (int i = 1; i <= n; i++) cin >> a[i];
	solve(1);
	cout << max(f[1][m][0], f[1][m][1]) << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值