[清华集训2012] 串珠子

[清华集训2012] 串珠子

题意

给定 n 个点和 n×n 的矩阵 c

ci,j 种方案把点 i 和点 j 连接起来。

求有多少种方案使得整张图连通。

思路

注意到 1n16,考虑状压。

定义 gi 为集合 i 的连边方案数,有

gi=u,vi(cu,v+1)

(u,v)ci,j 种方案连,有一种方案不连。

定义 fi 为集合 i 连通时的方案数,有

fi=gijigj×fCj

其中 C 是补集符号。

即枚举不连通的情况减去,枚举不连通的子集,

不连通子集连边的方案数乘上补集连通的方案数就是总方案数。

实现时注意固定 i 中的一个数和自己连通,其他点要么和他连通,要么不和他连通,这样才能不重不漏。

这里解释一下为什么是 gi×fCj 而不是 gi×gCjfi×gCj

这个转移方程本质上是把图分成两个互不连通的部分,

如果转移方程是第二种,就会出现重复,如下图。

这两种图本质相同,但由于没有限制一部分必须连通,就会被统计两遍。

所以必须限制一部分连通才能不重复。

因为我们固定了一个点和自己连通,如果交换 fg 后变成了自己和自己不连通,显然不成立。

答案为 f2n1

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 20;
int n, c[N][N];
ll f[1 << N], g[1 << N];
const int mod = 1e9 + 7;
int main() {
	cin >> n;
	for (int i = 0; i < n; i ++) 
		for (int j = 0; j < n; j ++) 
			cin >> c[i][j];
	for (int i = 0; i < (1 << n); i ++) {
		g[i] = 1;
		for (int j = 0; j < n; j ++)
			if (i >> j & 1) for (int k = j + 1; k < n; k ++)
				if (i >> k & 1) 
					g[i] = g[i] * (c[j][k] + 1) % mod;
	}
	for (int i = 0; i < (1 << n); i ++) {
		int I = i ^ (i & (-i));
		f[i] = g[i];
		for (int j = I; j; j = (j - 1) & I) {
			f[i] = f[i] - g[j] * f[i ^ j];
			f[i] = (f[i] % mod + mod) % mod;
		}
	}
	cout << f[(1 << n) - 1] << "\n";
	return 0;
} 
posted @   maniubi  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示