4596: [Shoi2016]黑暗前的幻想乡
Time Limit: 20 Sec Memory Limit: 256 MB
Submit: 464 Solved: 264
[Submit][Status][Discuss]
Description
四年一度的幻想乡大选开始了,最近幻想乡最大的问题是很多来历不明的妖
怪涌入了幻想乡,扰乱了幻想乡昔日的秩序。但是幻想乡的建制派妖怪(人类)
博丽灵梦和八云紫等人整日高谈所有妖怪平等,幻想乡多元化等等,对于幻想乡
目前面临的种种大问题却给不出合适的解决方案。
风间幽香是幻想乡里少有的意识到了问题的严重性的大妖怪。她这次勇敢的
站了出来参加幻想乡大选。提出包括在幻想乡边境建墙(并让人类出钱),大力
开展基础设施建设挽回失业率等一系列方案,成为了大选年出人意料的黑马并顺
利的当上了幻想乡的大统领。
幽香上台以后,第一项措施就是要修建幻想乡的公路。幻想乡有 N 个城市,
之间原来没有任何路。幽香向选民承诺要减税,所以她打算只修 N- 1 条路将
这些城市连接起来。但是幻想乡有正好 N- 1 个建筑公司,每个建筑公司都想
在修路的过程中获得一些好处。
虽然这些建筑公司在选举前没有给幽香钱,幽香还是打算和他们搞好关系,
因为她还指望他们帮她建墙。所以她打算让每个建筑公司都负责一条路来修。
每个建筑公司都告诉了幽香自己有能力负责修建的路是哪些城市之间的。所
以幽香打算选择 N-1 条能够连接幻想乡所有城市的边,然后每条边都交给一
个能够负责该边的建筑公司修建,并且每个建筑公司都恰好修一条边。
幽香现在想要知道一共有多少种可能的方案呢?两个方案不同当且仅当它
们要么修的边的集合不同,要么边的分配方式不同。
Input
第一行包含一个正整数 N(N<=17), 表示城市个数。
接下来 N-1 行,其中第 i行表示第 i个建筑公司可以修建的路的列表:
以一个非负数mi 开头,表示其可以修建 mi 条路,接下来有mi 对数,
每对数表示一条边的两个端点。其中不会出现重复的边,也不会出现自环。
Output
仅一行一个整数,表示所有可能的方案数对 10^9 + 7 取模的结果。
Sample Input
4
2 3 2 4 2
5 2 1 3 1 3 2 4 1 4 3
4 2 1 3 2 4 1 4 2
Sample Output
17
HINT
Source
这个题有点神
题目规定每个公司都必须修一条,所以我们考虑容斥,和它对着干
强行规定某些公司不修 ans=全都要修-至少一个不修+至少2个不修...
然后考虑排除一些公司不修之后的方案
无向图求生成树个数?被基尔霍夫支配的恐惧逐渐涌上心头。
对啦!基尔霍夫矩阵可以求。具体就是构造基尔霍夫矩阵,
然后去掉任意一行一列(行列标号相同)再对矩阵求行列式值。
从ZJ那里get到一些新东西:
求行列式的值可以把基尔霍夫矩阵高斯消元,保留上三角或者下三角然后对角线求积即可得出答案
然后我就滚去写了,,但是写的时候遇到一些问题:消元时double精度怎么办啊?取模怎么办啊?
从ZJ那里get到一些新东西:
欧几里得求解高斯消元。。O(n^3*log2(n))
具体是:在消元消到第i行时,把i+1~n行的i列位置都消成0
怎么消呢?
先知道行列式性质:
1.任意两行交换位置,行列式值变相反数
2.一行加上其他任意一行的整数倍,行列式值不变
考虑欧几里得把a[j][i]消成0:
把i行和j行辗转相除再相减直到a[j][i]为0啊,,
具体就是参照求gcd的做法啦,
先减,a[j][k]-=(a[i][i]/a[j][i])*a[i][k] (n>=k>=i)
再交换i,j行直到a[j][i]为0
参考一篇论文 https://wenku.baidu.com/view/d5e0e018964bcf84b9d57ba8.html
ZJ的此篇博客 http://www.cnblogs.com/zj75211/p/8035125.html
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#define ll long long
#define N 25
#define mod 1000000007
using namespace std;
int n,m,cnt[N];ll a[N][N];
struct edge{int u,v;};
vector<edge>g[N]; ll gauss(int x){
int fg=1;
for(int i=1;i<=x;i++)
for(int j=i+1;j<=x;j++)
while(a[j][i]!=0){
ll t=a[i][i]/a[j][i];
for(int k=i;k<=x;k++)
a[i][k]=(a[i][k]-a[j][k]*t)%mod;
for(int k=i;k<=x;k++)
swap(a[i][k],a[j][k]);
fg=-fg;
}
ll res=fg;
for(int i=1;i<=x;i++)res=res*a[i][i]%mod;
if(res<0)res+=mod;
return res;
} void build(int st){
memset(a,0,sizeof(a));
memset(cnt,0,sizeof(cnt));
for(int i=1;i<n;i++){
if(!(st&(1<<(i-1))))continue;
for(int j=0;j<g[i].size();j++){
edge x=g[i][j];
a[x.u][x.v]--;a[x.v][x.u]--;
cnt[x.u]++;cnt[x.v]++;
}
}
for(int i=1;i<=n;i++)a[i][i]=cnt[i];
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d",&m);
while(m--){
int a,b;
scanf("%d%d",&a,&b);
g[i].push_back((edge){a,b});
}
}
ll ans=0;
for(int st=0;st<(1<<(n-1));st++){
int sum=0;
for(int i=0;i<n-1;i++)if(st&(1<<i))sum++;
build(st);ll x=gauss(n-1);
ans+=((n-1-sum)&1?-1:1)*x;
ans%=mod;
}
ans<0?ans+=mod:1;
cout<<ans;
return 0;
}