UOJ#193. 【UR #14】人类补完计划(容斥原理+状压dp)

http://uoj.ac/problem/193

题解:

考虑先求出\(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();
}
posted @ 2020-07-30 19:30  Cold_Chair  阅读(305)  评论(0编辑  收藏  举报