P59330-[清华集训2012]串珠子【状压dp】

正题

题目链接:https://www.luogu.com.cn/problem/P5933


题目大意

n n n个点的一张无向图,求所有联通子图的权值乘积和


解题思路

因为 n n n很小,考虑状压
f i f_i fi表示点集为 i i i时的方案数,我们发现正着做十分麻烦,考虑容斥。
首先定义 m u l i mul_i muli表示点集 i i i的所有子图的方案数,那么显然有 m u l i = ∏ x , y ∈ i , x < y ( a x , y + 1 ) mul_i=\prod^{x,y\in i,xmuli=x,yi,x<y(ax,y+1)
枚举一个集合 s s s有转移方程,选出一个点 p p p一定在联通图内,这里状压为只有一个点 p p p的集合 f s = m u l s − ∑ i ∈ ( s − p ) f i + p ∗ m u l s − p − i f_s=mul_s-\sum_{i\in (s-p)}f_{i+p}*mul_{s-p-i} fs=mulsi(sp)fi+pmulspi
时间复杂度 O ( 3 n ) O(3^n) O(3n)


c o d e code code

#include
#include
#include
#define ll long long
using namespace std;
const ll N=17,XJQ=1e9+7;
ll n,mul[1<<N],f[1<<N],a[N][N];
int main()
{
	scanf("%lld",&n);
	for(ll i=0;i<n;i++)
		for(ll j=0;j<n;j++)
			scanf("%lld",&a[i][j]);
	ll MS=1<<n;
	for(ll s=0;s<MS;s++){
		mul[s]=1;
		for(ll i=0;i<n;i++){
			if(!((s>>i)&1))continue;
			mul[s]=mul[s^(1<<i)];
			for(ll j=i+1;j<n;j++)
				if(((s>>i)&1)&&((s>>j)&1))
					mul[s]=mul[s]*(a[i][j]+1)%XJQ;
			break;
		}
	}
	mul[0]=0;
	for(ll s=1;s<MS;s++){
		f[s]=mul[s];
		ll p=(s&-s),i=s-p;
		for(ll j=i;j>=0;j=(j==0?-1:((j-1)&i)))
			f[s]=(f[s]-f[j+p]*mul[i-j]%XJQ+XJQ)%XJQ;
	}
	printf("%lld",f[MS-1]);
}

你可能感兴趣的:(dp,luogu,状压dp)