这题好熟练啊
看到恰好想容斥这非常熟练吧
又发现数据范围这么小,显然是允许我们(2^n)暴力容斥的
于是我们枚举一个二进制状态(S),我们只使用包含在(S)这个状态里的公司的边,我们这样求出的就是至少有(n-|S|)个公司没有修建的方案数,容斥系数显然是((-1)^{n-|S|})
至于这个方案数怎么计算,交给矩阵树定理就好啦
复杂度(O(2^nn^3))
代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=18;
const int mod=1e9+7;
inline int read() {
char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n,cnt[131078],num[maxn],N;
int a[maxn][maxn];
std::vector<int> x[maxn],y[maxn];
inline int ksm(int a,int b) {
int S=1;
while(b) {if(b&1) S=(1ll*S*a)%mod;b>>=1;a=(1ll*a*a)%mod;}
return S;
}
inline int qm(int a) {return a>=mod?a-mod:a;}
inline int solve(int S) {
for(re int i=1;i<n;i++)
for(re int j=1;j<n;j++)
a[i][j]=0;
for(re int i=1;i<n;i++) {
if(!(S&(1<<(i-1)))) continue;
for(re int j=1;j<=num[i];j++) {
int u=x[i][j-1],v=y[i][j-1];
a[u][u]++,a[v][v]++;
a[u][v]--,a[v][u]--;
}
}
for(re int i=1;i<n;i++)
for(re int j=1;j<n;j++)
if(a[i][j]<0) a[i][j]+=mod;
int o=(cnt[S^N]&1),ans=1;
for(re int i=1;i<n;i++) {
int p;
for(p=i;p<n;++p) if(a[p][i]) break;
if(p==n) return 0;
if(i!=p) std::swap(a[p],a[i]),o^=1;
int Inv=ksm(a[i][i],mod-2);
for(re int j=i+1;j<n;j++) {
int t=(1ll*Inv*a[j][i])%mod;
for(re int k=i;k<n;k++)
a[j][k]=qm(a[j][k]-1ll*a[i][k]*t%mod+mod);
}
}
for(re int i=1;i<n;i++)
ans=(1ll*ans*a[i][i])%mod;
if(o) return qm(mod-ans);
return ans;
}
int main() {
n=read();N=(1<<(n-1))-1;
for(re int i=1;i<=N;i++) cnt[i]=cnt[i>>1]+(i&1);
for(re int i=1;i<n;i++) {
num[i]=read();
for(re int j=1;j<=num[i];j++)
x[i].push_back(read()),y[i].push_back(read());
}
int tot=0;
for(re int i=0;i<=N;i++)
tot=qm(tot+solve(i));
printf("%d
",tot);
return 0;
}