题意
铭铭有n个十分漂亮的珠子和若干根颜色不同的绳子。现在铭铭想用绳子把所有的珠子连接成一个整体。
现在已知所有珠子互不相同,用整数1到n编号。对于第i个珠子和第j个珠子,可以选择不用绳子连接,或者在ci,j根不同颜色的绳子中选择一根将它们连接。如果把珠子看作点,把绳子看作边,将所有珠子连成一个整体即为所有点构成一个连通图。特别地,珠子不能和自己连接。
铭铭希望知道总共有多少种不同的方案将所有珠子连成一个整体。由于答案可能很大,因此只需输出答案对1000000007取模的结果。
(n leq 16)
分析
参考SFN1036的题解。
题意即:给出一个大小为n的无向图,边有边权。定义一个子图的权值为所有边权的乘积,问所有使全部n个点连通的子图的权值和为多少。
设(f(s))表示使(s)中的点连通且不管其余点的所有子图的权值和为多少,(g(s))表示只看集合(s)中的点,所有子图的权值和是多少。
显然(g(s))可以预处理,考虑如何求(f(s))。
直接求显然不好求,考虑用所有方案减去不合法的方案。按照套路,我们可以枚举编号最小的点所在的连通块,设为集合(t),那么
[f(s)=g(s)-sum_{t}f(t) cdot g(s-t)
]
时间复杂度(O(n2^n + 3^n))
关于基准点的问题
不重
直接枚举子集会有重复的情况。具体而言重复的是“f枚举到了 原来的g的子集 ,然后g包含 原来的f 和 现在的f在原来的g中的补集 ”。
然后不找基准点的话就会重复,设想对于一种连通块个数确定的情况,会重复连通块个数那么多次。
然后确定了一个基准点以后,f就不可能再是g的子集了,所以不会重复。
不漏
由于不连通一定可以看成包含基准点的连通块与其他连通块不连通,所以不会遗漏。
无另解
每种情况的每个连通块都会作为t被计算一次,所以是连通块个数次。
但是所说的每种情况并未在状态中体现,所以虽然单个情况的确重复了连通块个数次,但是总体考虑是无法确定具体重复了多少次的。
也就是说没法通过不确定基准点计算,然后减去重复的情况这么做
%出题人
sto 出题人 orz
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<bitset>
#include<cassert>
#include<ctime>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
rg T data=0;
rg int w=1;
rg char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
{
data=data*10+ch-'0';
ch=getchar();
}
return data*w;
}
template<class T>T read(T&x)
{
return x=read<T>();
}
using namespace std;
typedef long long ll;
co int MAXN=16,mod=1e9+7;
int n,c[MAXN][MAXN];
int log2(int x)
{
int res=-1;
while(x)
{
x>>=1;
++res;
}
return res;
}
int add(int x,int y)
{
x+=y;
return x>=mod?x-mod:x;
}
int sub(int x,int y)
{
x-=y;
return x<0?x+mod:x;
}
int mul(int x,int y)
{
return (ll)x*y%mod;
}
int f[1<<MAXN],g[1<<MAXN];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n);
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
read(c[i][j]);
g[0]=1;
for(int i=1;i<(1<<n);++i)
{
int x=log2(i&-i);
// cerr<<"x="<<x<<endl;
g[i]=g[i-(1<<x)];
for(int j=0;j<n;++j)
if(i&(1<<j))
g[i]=mul(g[i],c[x][j]+1);
// cerr<<i<<" g="<<g[i]<<endl;
}
g[0]=0;
for(int i=1;i<(1<<n);++i)
{
f[i]=g[i];
int x=i&-i,s=i-x;
for(int j=s;j>=0;j=!j?-1:(j-1)&s)
f[i]=sub(f[i],mul(f[x+j],g[s-j]));
// cerr<<i<<" f="<<f[i]<<endl;
}
printf("%d
",f[(1<<n)-1]);
return 0;
}