「PKUWC2018」随机算法(状压dp)

传送门.


感觉这个状压题还是挺有意思的。

最基本的状压就是0表示没选,1表示选了不在独立集里,2表示选了在独立集里,枚举下一个点然后用位运算加速转移即可。

复杂度 O ( 3 n ∗ n ) O(3^n*n) O(3nn)

考虑其实1、2可以合并。

记0表示没选,1表示选了。

当选一个点到独立集时,就直接把它的邻点拿来提前安排了,这些点以后选的时候一定是不能放到独立集的,这样这个状态才是对的。

由于不知道独立集的大小,可能还需要记一维表示独立集的大小。

然后发现这一维不用记在状态里,因为只有局部最大独立集全局才能最大独立集,所以新记一个辅助状态即可。

O ( 2 n ∗ n ) O(2^n*n) O(2nn)

Code:

#include

#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;
}

int a2[21];
int n, m, x, y, z[21];
ll f[1 << 20]; int g[1 << 20], c[1 << 20];
ll fac[21], nf[21];

#define low(x) ((x) & -(x))

ll P(int n, int m) { return fac[n] * nf[n - m] % mo;}
int main() {
	a2[0] = 1; fo(i, 1, 20) a2[i] = a2[i - 1] * 2;
	scanf("%d %d", &n, &m);
	fac[0] = 1; fo(i, 1, n) fac[i] = fac[i - 1] * i % mo;
	nf[n] = ksm(fac[n], mo - 2); fd(i, n, 1) nf[i - 1] = nf[i] * i % mo;
	fo(i, 1, n) z[i] = a2[i - 1];
	fo(i, 1, m) {
		scanf("%d %d", &x, &y);
		z[x] += a2[y - 1]; z[y] += a2[x - 1];
	}
	f[0] = 1;
	ff(i, 1, a2[n]) c[i] = c[i - low(i)] + 1;
	ff(i, 0, a2[n]) if(f[i]) {
		int h = n; fo(j, 0, n - 1) h -= i >> j & 1;
		f[i] %= mo;
		fo(j, 1, n) if(!(i >> (j - 1) & 1)) {
			int ni = i | z[j], ng = g[i] + 1, F = f[i] * P(h - 1, c[z[j] - (z[j] & i)] - 1) % mo;
			if(ng > g[ni]) f[ni] = F, g[ni] = ng; else
			if(ng == g[ni]) f[ni] += F;
		}
	}
	ll ans = 0; int mg = 0;
	ff(i, 0, a2[n]) {
		if(g[i] > mg) mg = g[i], ans = f[i] % mo; else
		ans = (ans + f[i]) % mo;
	}
	pp("%lld\n", ans * nf[n] % mo);
}

你可能感兴趣的:(动态规划)