Codeforces53E Dead Ends Matrix_Tree定理 容斥原理

题意:给出一个n个点的无向图,求恰好有k个叶子的生成树个数 n<=10

Sol: 

如果是完全图,n个点的带标号生成树个数为n^{n-2} 可以用矩阵树定理或者打表+oeis证明

那么在完全图上求恰好k个叶子的生成树就考虑容斥,钦定有几个点一定是叶子,剩下的点用公式算,每个钦定的叶子都可以接在非钦定的点下面,乘起来在乘个组合数就行,复杂度可以做到O(nlogn) log是快速幂的复杂度

现在给出图的形态了,也可以考虑容斥,枚举钦定叶子的集合,剩下的点生成树个数可以用矩阵树定理算,钦定的点的方案也可以算,记录F(i)表示i集合的方案数

然后容斥就可以对于每个集合枚举包含包含她的集合,加加减减就可以了,枚举集合可以枚举补集的子集再或上现在的集合

总复杂度O(2^n*n^3+3^n)

因为矩阵树定理算行列式要高斯消元,精度问题还是有的,一开始不四舍五入一直过不去

Code:

#include<bits/stdc++.h>
#define debug(x) cout<<#x<<"="<<x<<endl
#define cls(x) memset(x,0,sizeof x)
typedef long long ll;
using namespace std;
const int maxn = 12;
const double eps = 1e-6;

int n,m,tar;
vector<int> line[maxn];
ll f[1<<maxn];
int idx[maxn];
bool ok[maxn];
double a[maxn][maxn];
ll ans;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

int count(int x)
{
	int res=0;
	while(x){if(x&1)res++;x>>=1;}
	return res;
}

double gauss(int len)
{
	double res=1;
	for(int i=1;i<=len;i++)
	{
		int now=i;
		while(now<=len&&fabs(a[now][i])<=eps) now++;
		if(now>len) continue;
		if(now!=i) for(int j=1;j<=len;j++) swap(a[now][j],a[i][j]);
		double tmp=a[i][i];res*=tmp;
		for(int j=1;j<=len;j++) a[i][j]/=tmp;
		for(int j=1;j<=len;j++) if(i!=j)
		{
			tmp=a[j][i];
			for(int k=1;k<=len;k++) a[j][k]-=tmp*a[i][k];
		}
	}
	for(int i=1;i<=len;i++) res*=a[i][i];
	return round(res);
}

ll calc(int sta)
{
	int cnt=count(sta),now=0;
	if(cnt<tar) return 0;
	cls(a);cls(ok);
	for(int i=1;i<=n;i++) if(!(sta&(1<<i>>1))) ok[i]=1,idx[i]=++now;
	for(int i=1;i<=n;i++) if(ok[i])
	{
		int sz=line[i].size();
		for(int j=0;j<sz;j++)
		{
			int w=line[i][j];
			if(ok[w]) a[idx[i]][idx[w]]-=1.0,a[idx[i]][idx[i]]+=1.0;
		}
	}
	ll res=1;
	for(int i=1;i<=n;i++) if(!ok[i])
	{
		int tmp=0,sz=line[i].size();
		for(int j=0;j<sz;j++) if(ok[line[i][j]]) tmp++;
		res*=tmp;
	}
	return res*gauss(now-1);
}

int main()
{
	n=read();m=read();tar=read();
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=read();
		line[x].push_back(y);
		line[y].push_back(x);
	}
	int mr=(1<<n)-1;
	for(int i=1;i<=mr;i++) f[i]=calc(i);
	for(int i=1;i<=mr;i++)
	{
		if(count(i)!=tar) continue;
		int sta=mr-i;
		for(int s=sta;s;s=(s-1)&sta)
		{
			if(count(s)&1) ans-=f[i+s];
			else ans+=f[i+s];
		}
		ans+=f[i];
	}
	cout<<ans<<endl;
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值