【洛谷P4336】黑暗前的幻想乡

题目:

题目链接:https://www.luogu.com.cn/problem/P4336
幽香上台以后,第一项措施就是要修建幻想乡的公路。幻想乡一共有 (n) 个城市,之前原来没有任何路。幽香向选民承诺要减税,所以她打算只修 (n-1) 条公路将这些城市连接起来。但是幻想乡有正好 (n-1) 个建筑公司,每个建筑公司都想在修路地过程中获得一些好处。虽然这些建筑公司在选举前没有给幽香钱,幽香还是打算和他们搞好关系,因为她还指望他们帮她建墙。所以她打算让每个建筑公司都负责一条路来修。
每个建筑公司都告诉了幽香自己有能力负责修建的路是哪些城市之间的。所以幽香打算 (n - 1) 条能够连接幻想乡所有城市的边,然后每条边都交给一个能够负责该边的建筑公司修建,并且每个建筑公司都恰好修建一条边。
幽香现在想要知道一共有多少种可能的方案呢?两个方案不同当且仅当它们要么修的边的集合不同,要么边的分配方式不同。
(nleq 17)

思路

首先如果没有每个公司只能建一条路的限制,直接上矩阵树定理即可。
有了这个限制之后,我们考虑容斥乱搞。
显然如果是至多 (i) 个公司建筑,容斥系数为 ((-1)^{n-i-1})
于是可以大力枚举所有 (2^{n-1}) 种方案,将基尔霍夫矩阵建出来,消元之后乘上容斥系数即可。
时间复杂度 (O(2^{n-1}n^3))

代码

#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
typedef long long ll;

const int N=20,M=(1<<17),MOD=1e9+7;
int n,lim,bit[M];
ll ans,g[N][N];
vector<pair<int,int> > e[N];

ll fpow(ll x,ll k)
{
	ll ans=1;
	for (;k;k>>=1,x=x*x%MOD)
		if (k&1) ans=ans*x%MOD;
	return ans;
}

ll det()
{
	ll res=1,f=1;
	for (int i=1;i<n;i++)
	{
		for (int j=i;j<n;j++)
			if (g[j][i])
			{
				if (i!=j) f=-f;
				for (int k=1;k<n;k++)
					swap(g[i][k],g[j][k]);
				break;
			}
		ll inv=fpow(g[i][i],MOD-2);
		for (int j=i+1;j<n;j++)
			if (g[j][i])
			{
				ll base=g[j][i]*inv%MOD;
				for (int k=1;k<n;k++)
					g[j][k]=((g[j][k]-base*g[i][k])%MOD+MOD)%MOD;
			}
	}
	for (int i=1;i<n;i++) res=res*g[i][i]%MOD;
	return (f*res%MOD+MOD)%MOD;
}

int main()
{
	scanf("%d",&n);
	for (int i=1,x;i<n;i++)
	{
		scanf("%d",&x);
		for (int j=1,y,z;j<=x;j++)
		{
			scanf("%d%d",&y,&z);
			e[i].push_back(mp(y,z));
		}
	}
	lim=(1<<n-1);
	for (int s=1;s<lim;s++)
	{
		bit[s]=bit[s-(s&-s)]+1;
		memset(g,0,sizeof(g));
		for (int i=1;i<=n;i++)
			if (s&(1<<i-1))
				for (int j=0;j<e[i].size();j++)
				{
					int x=e[i][j].first,y=e[i][j].second;
					g[x][y]--; g[y][x]--; g[x][x]++; g[y][y]++;
				}
		ans=(ans+((n-1-bit[s]&1)?-1:1)*det())%MOD;
	}
	printf("%lld",(ans%MOD+MOD)%MOD);
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/14233639.html