bzoj4671. 异或图(斯特林反演) 题目描述 题解 code

bzoj4671. 异或图(斯特林反演)
题目描述
题解
code

题解

迫真例题

(g[i])表示至少i个连通块的方案,(f[i])表示恰好i个连通块的方案(注意“至少”的含义)

则有(g[i]=sum_{j>=i}S(j,i)f[i])

斯特林反演:https://www.cnblogs.com/jz-597/p/13210825.html

类似子集反演,有(f[i]=sum_{j>=i}(-1)^{j-i}s(j,i)g[i])

考虑求g,枚举集合划分,不同集合里的点之间不能有边

把有边的图丢到线性基S里,答案是(2^{s-|S|}-1)

因为线性基里面的子集都不能选,全集挖掉线性基后的任意非空子集都可以选,选完后通过线性基里面的元素可以唯一使其合法

注意线性基只用考虑两端在不同集合的边

时间复杂度大概是(O(B_n*s*n^2))

code

#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define ll long long
//#define file
using namespace std;

int a[11][11],d[11],N,n,m,i,j,k,l;
ll p[61],f[61],b[61],g[11],ans;
char st[51];
bool Bz[51];

void add(ll t)
{
	int i,j,k,l;
	
	fd(i,m,1)
	if (t&p[i-1] && Bz[i])
	{
		if (!b[i]) {b[i]=t,--ans;break;}
		t^=b[i];
	}
}

void dg(int t,int tot)
{
	int i,j,k,l;
	bool bz;
	
	if (t>n)
	{
		memset(b,0,sizeof(b)),ans=N;
		fo(i,1,n-1) fo(j,i+1,n) Bz[a[i][j]]=d[i]!=d[j];
		fo(i,1,N)
		{
			bz=0;
			fo(j,1,n-1)
			{
				fo(k,j+1,n)
				if (d[j]!=d[k] && (f[i]&p[a[j][k]-1]))
				{add(f[i]);bz=1;break;}
				if (bz) break;
			}
		}
		
		if (tot==2)
		n=n;
		
		g[tot]+=p[ans]-1;
		return;
	}
	
	fo(i,1,tot) d[t]=i,dg(t+1,tot);
	d[t]=tot+1,dg(t+1,tot+1);
}

int main()
{
	#ifdef file
	freopen("bzoj4671.in","r",stdin);
	#endif
	
	p[0]=1;
	fo(i,1,60) p[i]=p[i-1]*2;
	
	scanf("%d",&N);
	fo(k,1,N)
	{
		scanf("%s",st+1);l=strlen(st+1);
		fo(i,1,l) f[k]+=p[i-1]*(st[i]=='1');
		
		if (!n)
		{
			while (n*(n-1)/2!=l) ++n;
			l=0;
			fo(i,1,n-1) fo(j,i+1,n) a[i][j]=++l;
		}
	}
	
	m=n*(n-1)/2;
	dg(1,0);
	
	ans=0;
	k=1;
	fo(j,1,n)
	{
		if (j>1) k*=j-1;
		ans+=(((j-1)&1)?-1:1)*g[j]*k;
	}
	printf("%lld
",ans);
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}