230425 归档 // 树形 DP

为什么没有空格?为什么没有空格?为什么没有空格?


A. 树的直径

http://222.180.160.110:1024/contest/3519/problem/1

对于一个根,递归地统计其下链长。选择最大者和次大者并合并之,即为树中最长链。

namespace XSC062 {
using namespace fastIO;
const int maxn = 1e5 + 5;
int f[maxn];
int n, x, y, mx, mx2;
std::vector<int> g[maxn];
inline int max(int x, int y) {
	return x > y ? x : y;
}
void DFS(int x, int fa) {
	for (auto i : g[x]) {
		if (i == fa)
			continue;
		DFS(i, x);
		f[x] = max(f[x], f[i]);
	}
	++f[x];
	return;
}
inline void add(int x, int y) {
	g[x].push_back(y);
	return;
}
int main() {
	read(n);
	for (int i = 1; i < n; ++i) {
		read(x), read(y);
		add(x, y), add(y, x);
	}
	DFS(1, -1);
	for (auto i : g[1]) {
		if (f[i] >= mx)
			mx2 = mx, mx = f[i];
		else if (f[i] > mx2)
			mx2 = f[i];
	}
	print(mx + mx2);
	return 0;
}
} // namespace XSC062

B. 求树的重心

http://222.180.160.110:1024/contest/3519/problem/2

只要在单次搜索结束后判断点连接的儿子部分和父亲部分是否大小都不大于一半即可。

namespace XSC062 {
using namespace fastIO;
const int maxn = 105;
int n, x, y;
int f[maxn];
std::vector<int> res;
std::vector<int> g[maxn];
void DFS(int x, int fa) {
	f[x] = 1;
	bool flag = 1;
	for (auto i : g[x]) {
		if (i == fa)
			continue;
		DFS(i, x);
		f[x] += f[i];
		if (f[i] > n / 2)
			flag = 0;
	}
	if (n - f[x] > n / 2)
		flag = 0;
	if (flag == 1)
		res.push_back(x);
	return;
}
inline void add(int x, int y) {
	g[x].push_back(y);
	return;
}
int main() {
	read(n);
	for (int i = 1; i < n; ++i) {
		read(x), read(y);
		add(x, y), add(y, x);
	}
	DFS(1, -1);
	print(res.size(), '\n');
	std::sort(res.begin(), res.end());
	for (auto i : res)
		print(i, '\n');
	return 0;
}
} // namespace XSC062

C. 树上染色

http://222.180.160.110:1024/contest/3519/problem/3

妙妙题。很好想到用背包处理问题,但问题在于后效性处理。

我一开始想到的是费用提前计算,假设一条边之下的子树中存在黑点 / 白点且它们的数量 \(x\) 不为 \(k\) / \(n - k\),即存在路径经过该点,即经过该边的路径数为 \(x\times(s-x)\),其中 \(s\) 的值为 \(k\)\(n-k\)

于是我们不难得到状态转移方程:

\[f_{x, j} = \max\{f_{x, j}, f_{x, j - l} + f_{i, l} + k \times (n - k)\times w + (s_i - l)\times [n - k - (s_i - l)]\times w\} \]

其中 \(w\) 为边权,\(s_i\) 为子树大小。

很快我就碰壁了。为了写着方便,我将 \(j\)\(k\) 同时倒序枚举,输出了预料之外的答案。输出中间变量,发现了原因:不同于一般的 01 背包,正常情况下 01 背包空间为 \(0\) 时价值也是 \(0\),但我们的这个背包的空间限制的其实是黑点的空间,白点仍然会带来价值,所以当我们枚举多个子树时,因为 \(0\le 0\)\(f_{v_1, 0}\) 将会更新 \(f_{v_2, 0}\)。然而对于白点来说,有可能我们使用的个数超过了总数,那么就 GG。一个非常简单的解决方案是正序枚举 \(l\)

但是由于历史遗留问题,我的树形背包的复杂度一直是错的 orz... 这次才彻底改对。所以我们的状态转移方程还需要微调。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int maxn = 2e3 + 5;
struct _ {
	int v, w;
	_() {}
	_(int v1, int w1) {
		v = v1, w = w1;
	}
};
int siz[maxn];
int f[maxn][maxn];
int n, k, x, y, w;
std::vector<_> g[maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline int max(int x, int y) {
	return x > y ? x : y;
}
void DFS(int x, int fa) {
	siz[x] = 1;
	for (auto i : g[x]) {
		if (i.v == fa)
			continue;
		DFS(i.v, x);
		for (int j = siz[x]; ~j; --j) {
			for (int l = siz[i.v]; ~l; --l) {
				int t = f[i.v][l];
				if (l != k && l)
					t += i.w * l * (k - l);
				if (siz[i.v] - l != n - k
								&& siz[i.v] - l) {
					t += i.w * (siz[i.v] - l) *
						(n - k - (siz[i.v] - l));
				}
				f[x][j + l] = max(f[x][j + l],
									f[x][j] + t);
			}
		}
		siz[x] += siz[i.v];
	}
	return;
}
inline void add(int x, int y, int w) {
	g[x].push_back(_(y, w));
	return;
}
int main() {
	read(n), read(k);
	for (int i = 1; i < n; ++i) {
		read(x), read(y), read(w);
		add(x, y, w), add(y, x, w);
	}
	DFS(1, -1);
	print(f[1][k]);
	return 0;
}
} // namespace XSC062
#undef int

口雷瓦,一百七十万年的智慧 得死!

(讶)

posted @ 2023-04-25 21:10  XSC062  阅读(19)  评论(0编辑  收藏  举报