某 SCOI 模拟赛 T1 s1mple【集合幂级数】

题意

给定 n × n n\times n n×n 的 01 矩阵 M M M q q q 次询问:给定长度 n − 1 n-1 n1 的 01 序列 a a a,求有多少 1 到 n n n 的排列 p p p 使得 ∀ i ∈ [ 1 , n − 1 ] M p i , p i + 1 = a i \forall i\in[1,n-1]M_{p_i,p_{i+1}}=a_i i[1,n1]Mpi,pi+1=ai n ≤ 17 n\leq 17 n17 q ≤ 1 0 5 q\leq10^5 q105

题解

先将 M M M 看作一个有向图,1 代表有边。

a a a 压缩为 S S S。记序列 S S S 的答案为 A [ S ] A[S] A[S] A A A 可以看成集合幂级数的形式。既有 1(必须有边)也有 0(必须无边)的限制显然不够好看,于是记 B [ S ] B[S] B[S] 代表 p p p 符合 S S S 中 1 的限制,不一定符合 S S S 中 0 的限制的排列数。即 B [ T ] = ∑ T ⊆ S A [ S ] B[T]=\sum\limits_{T\subseteq S}A[S] B[T]=TSA[S]。那么 B ( S ) B(S) B(S) 可以看作几个路径连起来, S S S 给出了每条路径依次该有多长的信息。求出 B B B 之后可以反演得到 A A A

g ( S ) g(S) g(S) 为集合 S S S 中的点可以形成的链的数量,显然它可以 O ( 2 n n 2 ) O(2^nn^2) O(2nn2) 预处理。记 f k [ S ] ( ∣ S ∣ = k ) = g ( S ) f_k[S](|S|=k)=g(S) fk[S](S=k)=g(S),将 f k f_k fk 看成集合幂级数的形式。

假如 S S S 对应的每条路径依次长 t i ( ∑ i = 1 l t i = n ) t_i(\sum\limits_{i=1}^{l}t_i=n) ti(i=1lti=n),那么 B [ S ] B[S] B[S] ∏ i = 1 l f t i \prod\limits_{i=1}^lf_{t_i} i=1lfti 中全集对应的系数(乘法为集合并卷积)。即,把对应长度的路径选出来,并且他们并集为所有顶点(没有重合)。

对于对应的每条路径长度相同的 S S S B [ S ] B[S] B[S] 也相同,所以只需要计算 A000041 ( n ) \text{A000041}(n) A000041(n) B [ S ] B[S] B[S] A000041 ( 17 ) = 297 \text{A000041}(17)=297 A000041(17)=297),每次的时间复杂度为 O ( 2 n n ) O(2^nn) O(2nn),只需要 O ( A000041 ( n ) 2 n n ) O(\text{A000041}(n)2^nn) O(A000041(n)2nn) 的复杂度便能计算出 B B B,然后计算出 A A A,然后 O ( 1 ) O(1) O(1) 询问。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
int popc(int x){
	return ( x     &1)+((x>>1 )&1)+((x>>2 )&1)+((x>>3 )&1)+
		   ((x>>4 )&1)+((x>>5 )&1)+((x>>6 )&1)+((x>>7 )&1)+
		   ((x>>8 )&1)+((x>>9 )&1)+((x>>10)&1)+((x>>11)&1)+
		   ((x>>12)&1)+((x>>13)&1)+((x>>14)&1)+((x>>15)&1)+
		   ((x>>16)&1);
}
const int N=18;
bool con[N][N];
ll f[N][1<<N];//f[i] 为大小为 i 的集合能组成链的数量的集合幂级数 
ll g[1<<N][N];//g[i][j] 为已经经过 i,现在在 j 的链数 
ll a[1<<N];
ll ans[1<<N];

int tmp[N],l;
int _(int x,int n){
	int t=l=0;
	for(int i=n-2;i>=0;--i){
		++t;
		if(((x>>i)&1)==0){
			tmp[l++]=t;
			t=0;
		}
	}
	tmp[l++]=t+1;
	sort(tmp,tmp+l);
	t=0;int ans=0;
	for(int i=0;i<l;i++){
		ans=(ans<<tmp[i])+((1<<tmp[i]-1)-1); 
	}
	return ans;
}

void fwt(ll *a,int n){
	for(int i=1;i<n;i<<=1)
		for(int j=0;j<n;j+=i<<1)
			for(int k=j;k<j+i;k++)
				a[k+i]+=a[k];
}
void ifwt(ll *a,int n){
	for(int i=1;i<n;i<<=1)
		for(int j=0;j<n;j+=i<<1)
			for(int k=j;k<j+i;k++)
				a[k+i]-=a[k];
}
void qaq(ll *a,int n){
	for(int i=1;i<n;i<<=1)
		for(int j=0;j<n;j+=i<<1)
			for(int k=j;k<j+i;k++)
				a[k]-=a[k+i];
}

int main(){
	int n=getint();
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			char c=getchar();
			while(c!='0'&&c!='1')c=getchar();
			con[i][j]=c-'0';
		} 
	f[0][0]=1;
	for(int i=0;i<(1<<n);i++){
		int h=popc(i);
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				int t=i^(1<<j);
				if(!t)g[i][j]=1;
				for(int k=0;k<n;k++)
					if(con[j][k])
						g[i][j]+=g[t][k];
				f[h][i]+=g[i][j]; 
			}
		}
		//cerr<<"> "<<h<<" "<<i<<" "<<f[h][i]<<endl;
	}
	
	for(int i=0;i<=n;i++){
		fwt(f[i],1<<n);
		//for(int j=0;j<1<<n;j++)cerr<<" "<<f[i][j];cerr<<endl;
	}
	
	memset(ans,-1,sizeof(ans));
	for(int i_=0;i_<(1<<n-1);i_++){
		int i=_(i_,n);
		//cerr<<"i "<<i_<<" -> "<<i<<endl;
		if(ans[i]!=-1){
			ans[i_]=ans[i];
			continue;
		}
		for(int j=0;j<(1<<n);j++)a[j]=1;
		for(int j=0;j<l;j++){
			for(int k=0;k<(1<<n);k++){
				a[k]*=f[tmp[j]][k];
			}
		}
		//for(int j=0;j<(1<<n);j++)cerr<<" "<<a[j];cerr<<endl;
		ifwt(a,1<<n);
		//for(int j=0;j<(1<<n);j++)cerr<<" "<<a[j];cerr<<endl;
		ans[i_]=ans[i]=a[(1<<n)-1];
	}
	//for(int i=0;i<1<<n-1;i++)cerr<<"| "<<ans[i];cerr<<endl;
	qaq(ans,1<<n-1);
	
	int q=getint();
	while(q--){
		int x=0;
		for(int i=1;i<n;i++){
			char c=getchar();
			while(c!='0'&&c!='1')c=getchar();
			x=(x<<1)|(c-'0');
		}
		//x=_(x,n);
		//cerr<<"> "<<x<<" "<<ans[x]<<endl;
		printf("%lld\n",ans[x]);
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值