题目描述:
emmm.
题目分析:
某位大仓鼠说过,计数问题就要考虑容斥…
图上的生成树问题可以N^3的跑矩阵树得到,重边是木有问题滴
我们统计出由n-1个公司建边的方案数
但是显然我们这样统计出的方案数包含了由n-2 公司建边的方案数
没关系,我们枚举到底是哪n-2个公司建造了这个树,显然这样的集合有 C(n-1,1) 种,建图的时候只加入这n-2个公司的边,对着这个图跑一边矩阵树就可以求出方案数了对吧~,然后依次减去这些集合的方案数
但是我们发现此时明显算少了,因为我们多减去了刚好n-3个公司建造的树,显然这样的集合有 C(n-1,2)种,我们继续枚举集合,建图的时候只加入这n-3个公司的边,然后继续跑矩阵树定理,此时我们依次加上这些集合的方案数
那么容斥方案即为 总的方案数 - 1个公司不建边的方案数 + 2个公司不建边的方案数 …
题目链接:
Ac 代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define ll long long
#define U link[i][j].u
#define V link[i][j].v
const int mod=1e9+7,maxm=20;
struct node{
int u,v;
};
std::vector <node> link[maxm];
int n,m;
ll a[maxm][maxm];
inline ll Gauss()
{
ll ret=1;
for(int i=2;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
while(a[j][i]!=0)
{
ll t=a[i][i]/a[j][i];
for(int k=i;k<=n;k++) a[i][k]=(a[i][k]-a[j][k]*t)%mod;
for(int k=i;k<=n;k++) std::swap(a[i][k],a[j][k]);
ret=-ret;
}
}
if(a[i][i]==0)return 0;
ret=(ret*a[i][i])%mod;
}
return (ret%mod+mod)%mod;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d",&m);
for(int j=1;j<=m;j++)
{
int u,v;
scanf("%d%d",&u,&v);
link[i].push_back((node){u,v});
}
}
ll ans=0;
for(int s=1;s<(1<<(n-1));s++)
{
int t=0;
memset(a,0,sizeof(a));
for(int i=1;i<n;i++)
if(s&(1<<(i-1)))
{
t++;
for(int j=0;j<link[i].size();j++)
{
a[U][V]--,a[V][U]--;
a[V][V]++,a[U][U]++;
}
}
int x=n-t-1;
if(x&1) ans=(ans-Gauss()+mod)%mod;
else ans=(ans+Gauss())%mod;
}
printf("%lld\n",ans);
return 0;
}