把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4336】[SHOI2016] 黑暗前的幻想乡(容斥+矩阵树定理)

点此看题面

  • \(n\)个点和\(n-1\)个公司,第\(i\)个公司可以修建\(m_i\)条给定边中的一条。
  • 要求每个公司修建恰好一条边,求形成一棵树的方案数。
  • \(n\le17,m_i\le\frac{n(n-1)}2\)

一种容斥套路

\(n-1\)个公司和\(n-1\)条边,恰好是一对一的关系。

针对这类问题有一种容斥套路,用\(f(S)\)表示只选用集合\(S\)中的公司,每个公司随意修多少条边,形成一棵树的方案数。

那么答案就应该是:

\[\sum_{S}(-1)^{n-|S|}f(S) \]

而要求\(f(S)\),只需把\(S\)中所有公司的边加到图上,问题就变成了求这张图的生成树个数,直接矩阵树定理就好了。

代码:\(O(2^{n-1}n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 17
#define X 1000000007
using namespace std;
int n,m[N+5];struct edge {int x,y;}e[N+5][N*N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int a[N+5][N+5];I int Det(CI n)//矩阵树定理
{
	RI i,j,k,o,t=1;for(i=1;i<=n;t=1LL*t*a[i][i]%X,++i)//t记录对角线元素乘积
	{
		if(!a[i][i]) {for(j=i+1;j<=n&&!a[j][i];++j);for(t=X-t,k=i;k<=n;++k) swap(a[i][j],a[i][k]);}//交换两行变换符号
		for(j=i+1;j<=n;++j) for(o=X-1LL*a[j][i]*QP(a[i][i],X-2)%X,k=i;k<=n;++k) a[j][k]=(1LL*o*a[i][k]+a[j][k])%X;//消元
	}return t;
}
int main()
{
	RI i,j,k;for(scanf("%d",&n),i=1;i^n;++i)
		for(scanf("%d",m+i),j=1;j<=m[i];++j) scanf("%d%d",&e[i][j].x,&e[i][j].y);
	RI op,x,y,t=0,l=1<<n-1;for(i=1;i^l;++i)//枚举用哪些公司
	{
		for(j=1;j^n;++j) for(k=1;k^n;++k) a[j][k]=0;//清空矩阵
		for(memset(a,0,sizeof(a)),op=j=1;j^n;++j) if(!(i>>j-1&1)) op=X-op;else//op记录容斥系数
			for(k=1;k<=m[j];++k) x=e[j][k].x,y=e[j][k].y,++a[x][x],++a[y][y],--a[x][y],--a[y][x];//把边表示到矩阵上
		for(j=1;j^n;++j) for(k=1;k^n;++k) a[j][k]<0&&(a[j][k]+=X);t=(1LL*op*Det(n-1)+t)%X;//容斥
	}return printf("%d\n",t),0;
}
posted @ 2021-04-07 16:32  TheLostWeak  阅读(48)  评论(0编辑  收藏  举报