洛谷 P6662 [POI 2019] Przedszkole
\(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 \log n)\)。
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;
}