UOJ#193. 【UR #14】人类补完计划(容斥原理+状压dp)
题解:
考虑先求出\(f[S]\)表示\(S\)这个点集的基环树个数。
可以先求\(f_{cur}[S]\)表示\(S\)点集成环的方案数:
\(dp[S][i]\)表示已选\(S\)中的点,最后一个是\(i\),的方案数,每次只能新加一个比\(S\)最小大的点,这样每个\(\ge 3\)环会被恰好记\(2\)次。
然后考虑类来DAG计数的方法,每次想扩展所有的叶子,可以枚举叶子的子集容斥。
对\(f[S]\),枚举\(T∩S=∅\),转移给\(f[S|T]\),乘上\(T\)连到\(S\)的方案数,和容斥系数\((-1)^{|T|+1}\)。
搞出了\(f\)之后看怎么求答案:
同样枚举叶子的子集容斥,
枚举两个集合\(S,T\),系数就是\(f[S]*(T到S的方案方案数)\),容斥系数\(2^{|S|}*(-1)^{|T|}\),通过二项式逆展开,发现会恰好算要算的东西。
\(T\)到\(S\)的方案数,可以\(2^n*n^2\)预处理。
所以复杂度:\(O(3^n)\),还可以子集卷积优化,不过很复杂。
Code:
#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i < _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;
const int mo = 998244353;
ll ksm(ll x, ll y) {
ll s = 1;
for(; y; y /= 2, x = x * x % mo)
if(y & 1) s = s * x % mo;
return s;
}
const ll inv2 = ksm(2, mo - 2);
const int N = 17;
int n, m, x, y;
int a[N][N];
int a2[N];
void add(ll &x, ll y) { (x += y) %= mo;}
ll xs1[1 << 16];
#define low(x) ((x) & -(x))
int c1[1 << 16];
int lg2[1 << 16];
namespace sub1 {
ll f[1 << 16][N];
int w[1 << 16];
void dp() {
ff(i, 1, a2[n]) c1[i] = c1[i ^ low(i)] + 1;
ff(i, 1, n) lg2[1 << i] = 1;
ff(i, 1, a2[n]) lg2[i] += lg2[i - 1];
ff(s, 1, a2[n]) {
ff(j, 0, n) if(s >> j & 1) {
w[s] = j + 1; break;
}
if(c1[s] == 1) {
f[s][w[s]] = 1;
}
}
ff(s, 1, a2[n]) {
fo(i, 1, n) if(f[s][i]) {
fo(j, 1, n) if(!(s >> (j - 1) & 1) && a[i][j] && j > w[s]) {
int ns = s | a2[j - 1];
add(f[ns][j], f[s][i]);
}
}
}
ff(s, 1, a2[n]) {
int cnt = 0;
ff(i, 0, n) cnt += s >> i & 1;
if(cnt <= 2) continue;
fo(i, 1, n) if(f[s][i] && a[i][w[s]]) {
ll v = f[s][i];
v = v * inv2 % mo;
add(xs1[s], v);
}
}
}
}
ll ans = 0;
namespace sub2 {
ll f[1 << 16];
ll g[1 << 16];
ll c[1 << 16][N];
void dp() {
ff(s, 1, a2[n]) {
f[s] = xs1[s];
fo(k, 1, n) {
fo(j, 1, n) if(s >> (j - 1) & 1)
c[s][k] += a[k][j];
}
}
ff(i, 1, a2[n]) {
g[0] = 1;
for(int j = (i + 1) | i; j < a2[n]; j = (j + 1) | i) {
int k = i ^ j;
g[k] = g[k ^ low(k)] * c[i][lg2[low(k)] + 1] % mo;
f[j] = (f[j] + f[i] * g[k] % mo * (c1[k] % 2 ? 1 : -1)) % mo;
}
}
ans = 0;
ff(i, 1, a2[n]) if(f[i]) {
g[0] = 1;
ans = (ans + f[i] * a2[c1[i]]) % mo;
for(int j = (i + 1) | i; j < a2[n]; j = (j + 1) | i) {
int k = i ^ j;
g[k] = g[k ^ low(k)] * c[i][lg2[low(k)] + 1] % mo;
ans = (ans + f[i] * g[k] % mo * a2[c1[i]] * (c1[k] % 2 ? -1 : 1)) % mo;
}
}
ans = (ans % mo + mo) % mo;
pp("%lld\n", ans);
}
}
int main() {
a2[0] = 1; fo(i, 1, 16) a2[i] = a2[i - 1] * 2;
scanf("%d %d", &n, &m);
fo(i, 1, m) {
scanf("%d %d", &x, &y);
a[x][y] = a[y][x] = 1;
}
sub1 :: dp();
sub2 :: dp();
}
转载注意标注出处:
转自Cold_Chair的博客+原博客地址