Solution
关于dp:
题意可以转换为:给出一个的无向图,边有边权。定义一个子图的权值为所有边权的乘积,问所有使全部 n个点连通的图的权值和为多少
f[s]表示当前联通状态为 s,
g[s]表示选 s的状态的点,连通性任意的方案数
那么 g[s]=i,j∈s∏(a[i][j]+1)
f[s]=g[s]−编号最小的点∈j,j⊊s∑f[j]g[s−j]
=>f[s]=g[s]−编号最小的点∈/j,j!=∅,j⊆s∑g[j]f[s−j]
其实不一定要编号最小,但是编号最小的点最容易取
关于子集枚举:
for (i=s;i;i=(i-1)&s)
- 正确性:
首先,因为有&s,所以 i一定是 s的子集
其次,因为每次 i只减 1,所以一定能枚举完 s的子集(具体不想讲了,二进制的东西讲起来麻烦,而且只要自己随便找个数模拟一下就好了) - 复杂度:
T=∑i=0n(in)2i−1( i表示 s中 1的个数)
<∑i=0n(in)2i=∑i=0n(n−in)2i=(1+2)n=3n
Code
#include<bits/stdc++.h>
using namespace std;
int n,i,j,f[1<<16],g[1<<16],a[16][16],s,M=1e9+7;
int main(){
scanf("%d",&n);
for (i=0;i<n;i++)
for (j=0;j<n;j++) scanf("%d",&a[i][j]),a[i][j]++;
for (s=0;s<1<<n;s++){
f[s]=1;
for (i=0;i<n;i++) if (s&(1<<i))
for (j=i+1;j<n;j++) if (s&(1<<j)) f[s]=1ll*f[s]*a[i][j]%M;
g[s]=f[s];
for (i=j=s^(s&-s);j;j=(j-1)&i) f[s]-=1ll*g[j]*f[s^j]%M,f[s]+=f[s]<0?M:0;
}
printf("%d",f[(1<<n)-1]);
}