串珠子[BZOJ2560]

题目描述

铭铭有 \(n\) 个十分漂亮的珠子和若干根颜色不同的绳子。现在铭铭想用绳子把所有的珠子连成一个整体。

现在已知所有的珠子互不相同,用整数 \(1\)\(n\) 编号。对于第 \(i\) 个珠子和第 \(j\) 个珠子,可以选择不用绳子连接,或者在 \(c_{i,j}\) 根不同颜色的绳子中选择一根将它们连接。如果把珠子看作点,把绳子看作边,将所有珠子连成一个整体即为所有点构成一个连通图。特别地,珠子不能和自己连接。

铭铭希望知道总共有多少种不同的方案将所有珠子连成一个整体。由于答案可能很大,因此只需输出答案对 \(1000000007\) 取模的结果。

输入格式

输入第一行包含一个正整数 \(n\),表示珠子的个数。接下来 \(n\) 行,每行包含 \(n\) 个非负整数,用空格隔开。这 \(n\) 行中,第 \(i\) 行第 \(j\) 个数为 \(c_{i,j}\)

输出格式

输出一行一个整数,为连接方案数对 \(1000000007\) 取模的结果。

\(n\le 20\)

题解

状压DP

首先我们设\(f[S]\)表示:\(S\)中包含的珠子两两之间任意连边(可以不连)的方案数

显然,\(f[S]=\Pi_{i\in S,j\in S} (c[i][j]+1)\)

这个可以在\(O(2^nn^2)\)的时间内预处理

但是这样求出的方案,不一定保证每个方案都是一个连通图

所以我们要再进行一次DP

\(g[S]\)表示:\(S\)中包含的珠子两两之间任意连边(可以不连),且构成一个连通块的方案数

初始时我们先把每个\(g[S]\)赋值成\(f[S]\),然后再删除掉不合法的部分

我们考虑\(1\)这颗珠子,在一个不合法的方案中,一定有部分珠子和\(1\)不在同一个连通块内

所以对于每个\(g[S]\),我们枚举\(S\)的一个不含珠子\(1\)的子集\(S_2\),表示\(S_2\)中的那些珠子和\(1\)不在同一个连通块内

这样的方案数有多少?首先,\(S_2\)内的珠子不一定要形成一整个连通块,所以是\(f[S_2]\)

其次,\(S-S_2\)即与\(1\)连通的珠子一定在一个连通块内,所以是\(g[S-S_2]\)

所以不合法的方案有\(f[S_2]*g[S-S_2]\)种,从\(g[S]\)中减去

所以我们可以得到:\(g[S]=f[S]-\sum f[S_2]*g[S-S_2]\) (\(S_2\)\(S\)的所有不含珠子\(1\)的子集)

答案即为\(g[2^n-1]\)

p.s. 我们发现所有的\(S-S_2\)都是含有珠子1的,所以为了降低复杂度,我们可以不去求那些不含珠子\(1\)\(g[S]\)

时间复杂度为枚举子集复杂度\(O(3^n)\) 但是大大小于这个上界

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

template<typename T>
inline void read(T &num) {
	T x = 0, ff = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') ff = -1;
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
	num = x * ff; 
}

const ll mod = 1000000007;
int n;
ll a[30][30], f[1050005], g[1050005]; 
//f:可以不联通  g:必须联通 

inline ll fpow(ll x, ll t) {
	ll ret = 1;
	for (; t; t >>= 1, x = x * x % mod) if (t & 1) ret = ret * x % mod;
	return ret;
}

int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			read(a[i][j]);
		}
	}
	for (int s = 0; s <= (1 << n) - 1; s++) {
		f[s] = 1;
		for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
			if (((s>>(i-1))&1) && ((s>>(j-1))&1)) {
				f[s] = f[s] * (a[i][j] + 1) % mod;
			}
		} 
	}
	memcpy(g, f, sizeof(g));
	for (int s = 1; s <= (1 << n) - 1; s++) {
		if (!(s & 1)) continue;
		for (int s2 = (s - 1) & s; s2; s2 = (s2 - 1) & s) {
			if (!(s2 & 1)) continue;
			g[s] = (g[s] - g[s2] * f[s-s2] % mod + mod) % mod;
		}
	}
	printf("%lld\n", g[(1<<n)-1]);
	return 0;
}
posted @ 2020-06-06 19:20  AK_DREAM  阅读(175)  评论(0编辑  收藏  举报