斯特林反演&[bzoj4671]异或图

9 篇文章 0 订阅
2 篇文章 0 订阅

前言

继续学习容斥的技巧!

题意简介

题面链接

题目大意

定义两个无重边无自环图 G 1 , G 2 G_1,G_2 G1,G2的异或为 G 3 G_3 G3( G 1 , G 2 , G 3 G_1,G_2,G_3 G1,G2,G3点数都为 n n n)
满足当边 ( u , v ) (u,v) (u,v) G 1 , G 2 G_1,G_2 G1,G2中共出现 1 1 1次时 G 3 G_3 G3中有边 ( u , v ) (u,v) (u,v),否则没有
现在给出 s s s个图,集合 S = { G 1 , G 2 , G 3 , ⋅ ⋅ ⋅ G s } S=\{G_1,G_2,G_3,···G_s\} S={G1,G2,G3,Gs}问有多少个 S S S的子集满足所有图的异或图联通

数据范围

n ≤ 10 , s ≤ 60 n\le10,s\le 60 n10,s60

题解

模拟

模拟大概是 Θ ( 2 s n 3 ) \Theta(2^sn^3) Θ(2sn3)的(如果n和s的数据范围交换就能过了
并没有什么用

正解

我们发现,直接维护是否联通并不方便
考虑容斥,记 f ( x ) f(x) f(x)为有至少有 x x x个联通块的方案数, g ( x ) g(x) g(x)为恰好有 x x x个联通快的方案数
我们发现,求 f ( x ) f(x) f(x)非常方便,我们只要枚举点集划分,保证一种划分中在不同的点集的点间一定没有边。我们发现,这样保证了点集之间一定不连通,但是不保证点集内部联通,所以就满足 f ( x ) f(x) f(x)的定义了
我们来列出 f ( x ) f(x) f(x) g ( x ) g(x) g(x)的关系

概念:第二类斯特林数是将n个不同的元素拆分成m个集合的方法数目。

那么,就可以得出: f ( x ) = ∑ i = x n { i x } g ( i ) f(x)=\sum_{i=x}^n\begin{Bmatrix}i\\x\end{Bmatrix}g(i) f(x)=i=xn{ix}g(i)
然后就可以斯特林反演得
g ( x ) = ∑ i = x n ( − 1 ) i − x [ i x ] f ( i ) g(x)=\sum_{i=x}^n(-1)^{i-x}\begin{bmatrix}i\\x\end{bmatrix}f(i) g(x)=i=xn(1)ix[ix]f(i)
由于我们要求的是 g ( 1 ) g(1) g(1)
g ( 1 ) = ∑ i = 1 n ( − 1 ) i − 1 [ i 1 ] f ( i ) g(1)=\sum_{i=1}^n(-1)^{i-1}\begin{bmatrix}i\\1\end{bmatrix}f(i) g(1)=i=1n(1)i1[i1]f(i)
容易发现 [ i 1 ] = ( i − 1 ) ! \begin{bmatrix}i\\1\end{bmatrix}=(i-1)! [i1]=(i1)!
所以 g ( 1 ) = ∑ i = 1 n ( − 1 ) i − 1 ( i − 1 ) ! f ( i ) g(1)=\sum_{i=1}^n(-1)^{i-1}(i-1)!f(i) g(1)=i=1n(1)i1(i1)!f(i)
我们就可以枚举点集划分,然后线性基计算方案即可(如果不会计算可以参考我的博客线性基
贝尔数 B n B_n Bn n n n个点的点集划分数量
具体 B 0 = B 1 = 1 B_0=B_1=1 B0=B1=1,当 n > 0 n>0 n>0 B n + 1 = ∑ i = 0 n B i B_{n+1}=\sum_{i=0}^{n}B_i Bn+1=i=0nBi
所以 B 10 = 115975 B_{10}=115975 B10=115975
总复杂度是 Θ ( B n n 4 s ) \Theta(B_nn^4s) Θ(Bnn4s)由于不满,所以博主满怀信心的写了一发
然后发现TLE了,最大数据大概要跑11s+,真是非常的悲惨
原来我用的方法是 Θ ( n 4 s ) \Theta(n^4s) Θ(n4s)求答案的(具体做法是把 s s s个长度为 n 2 n^2 n2的串放到线性基里,重要的是:我没有压位太懒了啊),所以导致复杂度过大
事实上这题可以不用这个复杂度,因为我们发现这个 s s s是小于 n 2 n^2 n2的,并且我们发现很多信息都不需要维护,所以我们得到了一个更加优越的 Θ ( n 2 s ) \Theta(n^2s) Θ(n2s)做法(统计每一位有哪些图符合条件来计算放进线性基里数的数量,由于此时的线性基是 s s s,所以更好压),那么总复杂度是 Θ ( B n n 2 s ) \Theta(B_nn^2s) Θ(Bnn2s)、大大减小,就能过了
注:其实算法一是可以用bitset压一波的,这里列出两个算法的复杂度
算法一: Θ ( B n n 2 s n 2 64 ) \Theta(B_nn^2s\frac{n^2}{64}) Θ(Bnn2s64n2)
算法二: Θ ( B n n 2 s s 64 ) \Theta(B_nn^2s\frac{s}{64}) Θ(Bnn2s64s)
可见算法二复杂度更小并且更方便,所以我最后写了算法二

斯特林反演

f i = ∑ j = 0 i { i j } g j ⇔ g i = ∑ j = 0 i ( − 1 ) i − j [ i j ] f j f_i=\sum_{j=0}^i\begin{Bmatrix}i\\j\end{Bmatrix}g_j\Leftrightarrow g_i=\sum_{j=0}^i(-1)^{i-j}\begin{bmatrix}i\\j\end{bmatrix}f_j fi=j=0i{ij}gjgi=j=0i(1)ij[ij]fj

这题用到的是一个变式:
f i = ∑ j = 0 i { n − j n − i } g j ⇔ g i = ∑ j = 0 i ( − 1 ) i − j [ n − j n − i ] f j f_i=\sum_{j=0}^i\begin{Bmatrix}{n-j}\\{n-i}\end{Bmatrix}g_j\Leftrightarrow g_i=\sum_{j=0}^i(-1)^{i-j}\begin{bmatrix}{n-j}\\{n-i}\end{bmatrix}f_j fi=j=0i{njni}gjgi=j=0i(1)ij[njni]fj
这个变式推一下就是题目里的那个了
证明:对于容斥原理&反演的思考和总结

代码

算法一的TLE代码,最大数据11s,正确性没有问题

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
int s,n,len;
struct Graph
{
	char s[101];
}G[61],nw,zc,Base[101];
int belo[11];
LL fac[11],ans;
inline void add(Graph&a,const Graph&b)
{
	for(rg int i=1;i<=len;i++)if(b.s[i]=='1')a.s[i]=a.s[i]=='0'?'1':'0';
}
inline LL build()
{
	int sum=0;
	for(rg int j=1;j<=s;j++)
	{
		Graph self=G[j];
		for(rg int i=1;i<=len;i++)
			if(nw.s[i]=='1'&&self.s[i]=='1')
			{
				if(Base[i].s[i]==0)
				{
					Base[i]=self,sum++;
					break;
				}
				else add(self,Base[i]);
			}
	}
	for(rg int i=1;i<=len;i++)if(nw.s[i]=='1')memset(Base[i].s,0,sizeof(Base[i].s));
	return 1ll<<(s-sum);
}
void dfs(const int step,const int maxnum)
{
	if(step==n+1)
	{
		int tot=0;
		for(rg int i=1;i<=n;i++)
			for(rg int j=i+1;j<=n;j++)
				if(belo[i]!=belo[j])nw.s[++tot]='1';
				else nw.s[++tot]='0';
		ans+=fac[maxnum-1]*build();
		return;
	}
	for(rg int i=1;i<=maxnum;i++)belo[step]=i,dfs(step+1,maxnum);
	belo[step]=maxnum+1,dfs(step+1,maxnum+1);
}
int main()
{
	fac[0]=1;
	for(rg int i=1;i<=10;i++)fac[i]=-fac[i-1]*i;
	read(s);
	for(rg int i=1;i<=s;i++)scanf("%s",G[i].s+1);
	len=strlen(G[1].s+1);
	for(rg int i=1;i<=10;i++)
		if(i*(i-1)/2==len)
		{
			n=i;
			break;
		}
	dfs(1,0);
	print(ans); 
	return 0;
}

贴上AC代码

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
int s,n,len;
struct Graph
{
	char s[101];
}G[61];
int belo[11];
LL Base[101],top,fac[11],ans;
void dfs(const int step,const int maxnum)
{
	if(step==n+1)
	{
		int tot=0;top=0;
		for(rg int i=1;i<=n;i++)
			for(rg int j=i+1;j<=n;j++)
			{
				tot++; 
				if(belo[i]!=belo[j])
				{
					LL zt=0,each=1;
					for(rg int k=1;k<=s;k++,each<<=1)if(G[k].s[tot]=='1')zt|=each;
					for(rg int k=1;k<=top;k++)if((zt^Base[k])<zt)zt^=Base[k];
					if(zt)Base[++top]=zt;
				}
			}
		ans+=fac[maxnum-1]*(1ll<<(s-top));
		return;
	}
	for(rg int i=1;i<=maxnum;i++)belo[step]=i,dfs(step+1,maxnum);
	belo[step]=maxnum+1,dfs(step+1,maxnum+1);
}
int main()
{
	fac[0]=1;
	for(rg int i=1;i<=10;i++)fac[i]=-fac[i-1]*i;
	read(s);
	for(rg int i=1;i<=s;i++)scanf("%s",G[i].s+1);
	len=strlen(G[1].s+1);
	for(rg int i=1;i<=10;i++)
		if(i*(i-1)/2==len)
		{
			n=i;
			break;
		}
	dfs(1,0);
	print(ans); 
	return 0;
}

总结

这里这个线性基的技巧非常巧妙(甚至感觉这简直是在考线性基的相关应用),然后斯特林反演其实也算是容斥了一波,只在推式子的过程中用到(但是不得不说这样的模型不知为何用到的特别多),整道题还是非常清新的

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值