题面
题意:有n个点,n-1组边,问每组中选一条边,构成一棵树的方案数。
n≤17
相当于有n-1个条件,每个条件要求要在这一组中选边,求满足n-1个要求的方案数。
我们可以一本正经的上容斥
设
f[S]
为集合S中的条件不满足(其他任意)的方案数
g[S]
为只有集合S中的条件不满足的方案数
f[S]
能用矩阵树定理
n3
求出来
根据套路
g[S]=∑S⊆N(−1)|N|−|S|f[N]
g[0]
即为答案
突然发现大佬们的行列式都是写辗转相除,只有我是写逆元的
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))
typedef long long LL;
const LL mo=1e9+7;
LL B[20][20];
int n,m[20],x[20][400],y[20][400];
LL ans;
LL cheng(LL a,LL b)
{
LL res=1;
for(;b;b>>=1,a=a*a%mo)
if(b&1)
res=res*a%mo;
return res;
}
LL det(int x)
{
for(int i=1;i<=x;i++)
for(int j=1;j<=x;j++)
B[i][j]=(B[i][j]+mo)%mo;
bool rev=0;
LL res=1;
for(int i=1;i<x;i++)
{
for(int hy=i+1;hy<=x;hy++)
if(B[i][hy])
{
rev^=1;
for(int j=1;j<=x;j++)
swap(B[j][i],B[j][hy]);
break;
}
if(!B[i][i])
return 0;
LL inv=cheng(B[i][i],mo-2);
for(int j=i+1;j<=x;j++)
if(B[j][i])
{
LL hy=inv*B[j][i]%mo;
for(int k=i+1;k<=x;k++)
B[j][k]=(B[j][k]-hy*B[i][k]%mo+mo)%mo;
}
}
for(int i=1;i<=x;i++)
res=res*B[i][i]%mo;
if(rev)
res=(mo-res)%mo;
return res;
}
LL ok(int x)
{
bool hy=0;
for(;x;x-=x&-x)
hy=hy^1;
if(hy)
return mo-1;
return 1;
}
int main()
{
cin>>n;
for(int i=0;i<=n-2;i++)
{
scanf("%d",&m[i]);
for(int j=1;j<=m[i];j++)
scanf("%d%d",&x[i][j],&y[i][j]);
}
int nn=1<<(n-1);
for(int i=0;i<nn;i++)
{
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
B[j][k]=0;
for(int j=0;j<=n-2;j++)
if(((1<<j)&i)==0)
{
for(int k=1;k<=m[j];k++)
{
B[x[j][k]][y[j][k]]--;
B[y[j][k]][x[j][k]]--;
B[x[j][k]][x[j][k]]++;
B[y[j][k]][y[j][k]]++;
}
}
ans=(ans+det(n-1)*ok(i)%mo)%mo;
}
cout<<ans<<endl;
return 0;
}