1. 程式人生 > >矩陣樹定理+容斥--luoguP4336 [SHOI2016]黑暗前的幻想鄉

矩陣樹定理+容斥--luoguP4336 [SHOI2016]黑暗前的幻想鄉

傳送門

把所有公司的合法邊都連起來,構成一個無向圖
合法的樹需要滿足兩個條件:
是圖的生成樹
每個邊分給不同的公司

直接算會算重,考慮容斥
可以用總數-一個公司沒有+兩個公司沒有-三個…
每一次都要做一遍高斯消元,複雜度是 O ( 2 n

1 × ( n 1 ) 3 ×
l o g ( n 1 ) ) O(2^{n-1}\times (n-1)^3\times log(n-1))

看起來十分不可過,但就是過了

關於矩陣樹定理可以看這裡

程式碼如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 20
#define LL long long
using namespace std;

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

const int maxn=(1<<17)+5;
const int mod=1e9+7;
int n,a[N][N],ed,ans;
vector< pair<int,int> > vec[N];

inline LL qpow(LL x,int k){
	LL ret=1;
	while(k){
		if(k&1) (ret*=x)%=mod;
		(x*=x)%=mod; k>>=1;
	} return ret%mod;
}

inline int Matrix_tree(){
	for(int i=1;i<n;i++)
		for(int j=1;j<n;j++)
			a[i][j]=(a[i][j]+mod)%mod;
	int ans=1,f=1;
	for(int i=1;i<n;i++){
		for(int j=i+1;j<n;j++){
			if(!a[j][i]) continue;
			int A=a[i][i],B=a[j][i];
			while(B){
				int t=A/B;
				A%=B; swap(A,B);
				for(int k=i;k<n;k++)
					a[i][k]=(a[i][k]-1LL*t*a[j][k]%mod+mod)%mod;
				for(int k=i;k<n;k++) swap(a[i][k],a[j][k]);
				f=-f;
			}
		}
		if(!a[i][i]) return 0;
		ans=1LL*ans*a[i][i]%mod;
	}
	if(f==-1) ans=mod-ans;
	return ans%mod;
}

int main(){
	n=rd(); ed=1<<(n-1);
	for(int i=1;i<n;i++){
		int m=rd();
		for(int j=1;j<=m;j++){int x=rd(),y=rd(); vec[i].push_back(make_pair(x,y));}
	}
	for(int i=0;i<ed;i++){
		memset(a,0,sizeof a); int cnt=0;
		for(int j=1;j<n;j++)
			if((1<<(j-1))&i){
				++cnt;
				for(int k=0;k<vec[j].size();k++){
					int u=vec[j][k].first,v=vec[j][k].second;
					a[u][u]++,a[u][v]--; a[v][v]++,a[v][u]--;
				}
			}
		if((n-1-cnt)&1) ans-=Matrix_tree();
		else ans+=Matrix_tree();
		ans=(ans%mod+mod)%mod;
	}
	printf("%d\n",ans);
	return 0;
}