【贝尔数+容斥+斯特林反演+线性基】BZOJ4671异或图

【题目】
lydsy
给定 S S S个有 n n n个节点的图,求有多少个子集的异或为一个连通图。
n ≤ 10 , S ≤ 60 n\leq 10,S\leq 60 n10,S60

【题目】
不会。

首先连通并不好做,考虑求不连通有多少个。那么先花费贝尔数的时间枚举一个划分,表示不同划分里的点在不同的连通块,但同一个划分里的点不一定在同一个连通块。也就是说所有连接两个不同划分的边都必须为 0 0 0,方案数可以通过线性基求自由元个数求出来。

接下来需要对这个东西进行容斥,我们设 f i f_i fi表示至少有 i i i个连通块的方案数, g i g_i gi表示恰好有 i i i个连通块的方案数,那么我们上面求的就是 f f f

考虑容斥系数要考虑 f f f g g g的关系,我们考虑一幅图如果实际上被分成了 m m m个连通块,那么它会被计算多少次:
∑ i = 1 m S ( m , i ) \sum_{i=1}^m S(m,i) i=1mS(m,i)
于是我们有:
f i = ∑ j = i n g j ⋅ S ( j , i ) f_i=\sum_{j=i}^n g_j\cdot S(j,i) fi=j=ingjS(j,i)
然后斯特林反演:
g i = ∑ j = i n ( − 1 ) j − i ⋅ s ( j , i ) ⋅ f j g_i=\sum_{j=i}^n (-1)^{j-i}\cdot s(j,i)\cdot f_j gi=j=in(1)jis(j,i)fj
可以得到:
g 1 = ∑ i = 1 n ( − 1 ) i − 1 ⋅ ( i − 1 ) ! ⋅ f i g_1=\sum_{i=1}^n(-1)^{i-1}\cdot (i-1)!\cdot f_i g1=i=1n(1)i1(i1)!fi
然后就没有了,每次重新计算 f f f套柿子即可。

复杂度 O ( B n ⋅ n 3 ) O(B_n\cdot n^3) O(Bnn3)

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=12,M=65;
int S,n,m,id[N];
char s[N*N];
bool e[M][N][N];
ll ans,fac[N];

namespace Base
{
	int cnt;ll A[M];
	void insert(ll x)
	{
		for(int i=S-1;~i;--i) if(x&(1ll<<i))
		{
			if(!A[i]) {A[i]=x;++cnt;return;}
			else x^=A[i];
		}
	}
	ll calc()
	{
		memset(A,0,sizeof(A));cnt=0;
		for(int i=1;i<n;++i) for(int j=i+1;j<=n;++j)
			if(id[i]^id[j]) 
			{
				ll now=0;
				for(int k=0;k<S;++k) now|=(1ll<<k)*e[k][i][j];
				insert(now);
			}
		//for(int i=1;i<=n;++i) printf("%d ",id[i]); puts("");
		//printf("%d\n",cnt);
		return 1ll<<(S-cnt);
	}
}

void dfs(int d,int x)
{
	if(d==n+1) {ans+=(x&1?1:-1)*Base::calc()*fac[x-1];return;}
	for(int i=1;i<=x;++i) id[d]=i,dfs(d+1,x);
	id[d]=++x;dfs(d+1,x);
}

int main()
{
#ifdef Durant_Lee
	freopen("BZOJ4671.in","r",stdin);
	freopen("BZOJ4671.out","w",stdout);
#endif
	scanf("%d",&S);
	for(int i=0,m;i<S;++i)
	{
		scanf("%s",s);m=strlen(s);
		n=ceil(sqrt(m*2));
		for(int j=1,x=0;j<n;++j) for(int k=j+1;k<=n;++k,++x) e[i][j][k]=s[x]^48;
	}
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i;
	dfs(1,0);printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值