洛谷 P6662 [POI 2019] Przedszkole

发布时间 2023-11-14 15:31:19作者: zltzlt

洛谷传送门

\(k\) 染色问题。给定 \(n\) 个点 \(m\) 条边无向图,求有多少种给每个点赋点权 \(a_u \in [1, k]\) 的方案,使得 \(\forall (u, v) \in E, a_u \ne a_v\)

Subtask \(1\)\(n \le 15\)

考虑因为最终只会用到最多 \(n\) 种颜色,所以设恰好用了 \(t\) 种颜色,把 \(k\) 种颜色映射到 \([1, t]\) 中,方案数为 \(\binom{k}{t}\);然后再状压 dp,设 \(f_{i, S}\) 表示已经用了前 \(i\) 种颜色,当前 \(S\) 集合中的点已经染色。转移枚举 \(S\) 的严格超集即可。时间复杂度 \(O(n3^n + qn)\)

Subtask \(2\)\(m \le 24\)

实际上这是 ABC294Ex K-Coloring。 考虑一些与边数有关的指数级算法。考虑容斥,钦定 \(S \subseteq E\) 中的 \((u, v)\) 必须满足 \(a_u = a_v\)。设最后连完 \(S\) 中的边后连通块数为 \(c\),答案即 \(\sum\limits_{S \subseteq E} (-1)^{|S|} k^c\)。对于每个 \(c\) 预处理有多少个边集最后会形成 \(c\) 个连通块,即可 \(O(m)\) 回答单次询问。同时因为 \(m\) 很大要注意实现精细。可以 dfs 枚举每条边是否进入 \(S\),一个简单的剪枝是递归到一条边时 \(u, v\) 在同一个连通块就立刻退出,因为选和不选的贡献抵消。时间复杂度 \(O(n2^m + qm)\)

Subtask \(3\):每个连通块是一个环。

这是 ABC307E Distinct Adjacent。每个环相互独立,所以只用考虑一个环,最后方案数乘起来。设 \(f_n\)\(n\) 个点的环的方案数,转移考虑删掉点 \(n\),如果点 \(1\) 和点 \(n - 1\) 颜色相同那么把它们缩起来,同时点 \(n\)\(n - 1\) 种选颜色的方案;否则点 \(n\)\(k - 2\) 种选颜色的方案,因为点 \(1\) 和点 \(k - 1\) 颜色不同所以可以直接删掉点 \(n\)。因此有 \(f_n = (k - 1) f_{n - 2} + (k - 2) f_{n - 1}\)。这个东西还有通向公式 \(f_n = (k - 1)^n + (k - 1) (-1)^n\)。时间复杂度 \(O(nq + m)\)

code
// Problem: P6662 [POI 2019] Przedszkole
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6662
// Memory Limit: 128 MB
// Time Limit: 1500 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<int, int> pii;

const int maxn = 100100;
const ll mod = 1000000007;

inline ll qpow(ll b, ll p) {
	ll res = 1;
	while (p) {
		if (p & 1) {
			res = res * b % mod;
		}
		b = b * b % mod;
		p >>= 1;
	}
	return res;
}

ll n, m, q;
pii E[maxn];
vector<int> G[maxn];

inline void upd(ll &x, ll y) {
	((x += y) >= mod ? x -= mod : 0);
}

namespace Sub1 {
	const int maxm = (1 << 15) + 50;
	
	ll f[19][maxm], fac[maxn], ifac[maxn];
	bool g[maxm];
	
	inline ll C(ll n, ll m) {
		if (n < m || n < 0 || m < 0) {
			return 0;
		} else {
			ll res = ifac[m];
			for (ll i = n - m + 1; i <= n; ++i) {
				res = res * i % mod;
			}
			return res;
		}
	}
	
	void solve() {
		for (int S = 1; S < (1 << n); ++S) {
			g[S] = 1;
			for (int i = 1; i <= n; ++i) {
				if (S & (1 << (i - 1))) {
					for (int j : G[i]) {
						if (S & (1 << (j - 1))) {
							g[S] = 0;
							break;
						}
					}
				}
			}
		}
		f[0][0] = 1;
		for (int i = 0; i < n; ++i) {
			for (int S = 0; S < (1 << n); ++S) {
				if (!f[i][S]) {
					continue;
				}
				for (int T = (S + 1) | S; T < (1 << n); T = (T + 1) | S) {
					if (g[S ^ T]) {
						upd(f[i + 1][T], f[i][S]);
					}
				}
			}
		}
		fac[0] = 1;
		for (int i = 1; i <= n; ++i) {
			fac[i] = fac[i - 1] * i % mod;
		}
		ifac[n] = qpow(fac[n], mod - 2);
		for (int i = n - 1; ~i; --i) {
			ifac[i] = ifac[i + 1] * (i + 1) % mod;
		}
		while (q--) {
			ll x, ans = 0;
			scanf("%lld", &x);
			for (int i = 1; i <= min(n, x); ++i) {
				ans = (ans + C(x, i) * f[i][(1 << n) - 1]) % mod;
			}
			printf("%lld\n", ans);
		}
	}
}

namespace Sub2 {
	ll f[maxn];
	int fa[maxn];
	
	int find(int x) {
		return fa[x] == x ? x : find(fa[x]);
	}
	
	void dfs(int d, int cnt, int op) {
		if (d > m) {
			f[cnt] += op;
			return;
		}
		int x = find(E[d].fst), y = find(E[d].scd);
		if (x == y) {
			return;
		}
		fa[x] = y;
		dfs(d + 1, cnt - 1, -op);
		fa[x] = x;
		dfs(d + 1, cnt, op);
	}
	
	void solve() {
		for (int i = 1; i <= n; ++i) {
			fa[i] = i;
		}
		mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
		for (int i = 1; i <= m; ++i) {
			if (rnd() & 1) {
				swap(E[i].fst, E[i].scd);
			}
		}
		dfs(1, n, 1);
		while (q--) {
			ll x, ans = 0;
			scanf("%lld", &x);
			for (int i = n; i >= max(1LL, n - 60); --i) {
				ans = (ans + (f[i] + mod) * qpow(x, i)) % mod;
			}
			printf("%lld\n", ans);
		}
	}
}

namespace Sub3 {
	int fa[maxn], sz[maxn];
	
	int find(int x) {
		return fa[x] == x ? x : fa[x] = find(fa[x]);
	}
	
	inline void merge(int x, int y) {
		x = find(x);
		y = find(y);
		if (x != y) {
			fa[x] = y;
			sz[y] += sz[x];
		}
	}
	
	inline ll calc(ll n, ll m) {
		return (qpow(m - 1, n) + ((n & 1) ? mod - (m - 1) : m - 1)) % mod;
	}
	
	void solve() {
		for (int i = 1; i <= n; ++i) {
			fa[i] = i;
			sz[i] = 1;
		}
		for (int i = 1; i <= m; ++i) {
			merge(E[i].fst, E[i].scd);
		}
		vector<ll> vc;
		for (int i = 1; i <= n; ++i) {
			if (fa[i] == i) {
				vc.pb(sz[i]);
			}
		}
		while (q--) {
			ll x;
			scanf("%lld", &x);
			ll ans = 1;
			for (ll y : vc) {
				ans = ans * calc(y, x) % mod;
			}
			printf("%lld\n", ans);
		}
	}
}

void solve() {
	scanf("%lld%lld%lld", &n, &m, &q);
	for (int i = 1, u, v; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		G[u].pb(v);
		G[v].pb(u);
		E[i] = mkp(u, v);
	}
	if (n <= 15) {
		Sub1::solve();
		return;
	}
	if (m <= 24) {
		Sub2::solve();
		return;
	}
	Sub3::solve();
}

int main() {
	int T = 1;
	// scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}