CF1929E 题解

题意:

给定一棵 \(n\) 个节点数和 \(k\) 条路径 \((a_i, b_i)\),求至少将多少条边染色,使得给定路径都至少有一条染色的边。

\(n \le 10^5, k \le 20\)

思路:

好题。

显然状压 \(dp\)\(dp[S]\) 表示至少染多少条边使得 \(S\) 中的路径都满足条件。正常思路是枚举子集转移,考虑 \(T \sub S\) 能否有一条边同时经过 \(T\) 中所有路径,但是这样是 \(O(3^n)\) 的。

考虑建出这 \(2k\) 个点的虚树,我们定义 \(V_i\) 表示第 \(i\) 条边经过的所有路径,结合虚树的知识,不难发现本质不同的 \(V_i\)\(O(k)\) 的。

于是我们考虑用这些转移,采用刷表法,写成 \(dp[S | v_i] = min(dp[S | v_i], dp[S] + 1)\),这样时间复杂度就是 \(O(k2^k)\) 的了。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;

int n, k;
vector<int> e[N];

int d[N] = {0};
int p[N] = {0};

void dfs(int x, int pr, int dth) {
	p[x] = pr, d[x] = dth;
	for (auto i: e[x])
		if (i != pr)
			dfs(i, x, dth + 1);
}

int val[N] = {0};
 
void upd(int a, int b, int v) {
	if (a == b)	
		return;
	if (d[a] < d[b])
		swap(a, b);
	val[a] += v;
	upd(p[a], b, v);
}
 
int dp[(1 << 20) + 5] = {0};
 
void slv() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		val[i] = 0, e[i].clear();
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1, 1, 1);
	cin >> k;
	for (int i = 1, a, b; i <= k; i++) {
		cin >> a >> b;
		upd(a, b, (1 << (i - 1)));
	}
	sort(val + 1, val + n + 1);
	int cur = unique(val + 1, val + n + 1) - val - 1;
	
	for (int S = 0; S < (1 << k); S++) 
		dp[S] = 2e9;
	dp[0] = 0;
	for (int S = 0; S < (1 << k); S++)
		for (int i = 1; i <= cur; i++)
			dp[(S | val[i])] = min(dp[(S | val[i])], dp[S] + 1);
	cout << dp[(1 << k) - 1] << endl;
}

int main() {
	int T;
	cin >> T;
	while (T--)
		slv();
	return 0;
} 
posted @ 2024-02-17 14:52  rlc202204  阅读(10)  评论(0编辑  收藏  举报