JZOJ6724. 【2020.06.15省选模拟】T1

本文深入探讨了子集卷积和快速沃尔什变换(FWT)算法在解决特定组合数学问题中的应用。通过详细的代码示例,解释了如何使用FWT进行子集卷积,以及如何通过逆FWT(IFWT)回溯解决方案。文章重点介绍了子集卷积的概念,以及FWT在高效处理子集操作中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

在这里插入图片描述
n < = 17 , Q < = 1 e 5 n<=17,Q<=1e5 n<=17,Q<=1e5

Solution

  • 考虑容斥,设一个长度为 n − 1 n-1 n1的0/1串,0的位置 a i = 0 / 1 a_i=0/1 ai=0/1,1的位置 a i = 1 a_i=1 ai=1,这个状态对应的 a i a_i ai的排列的方案数。
  • 那么对于一个 00100110110... 00100110110... 00100110110...,相邻的 1 1 1表示这条链一定里面全是1, 0 0 0则没有限制,那么这个串就把 n n n个点分成了若干个互补相关的链。
  • 考虑这种分法一共有 n n n的整数划分 P ( n ) = 297 P(n)=297 P(n)=297种,可以暴力所有的分法,那么要求所有的链的并集为 n n n
  • 如果我们把每一种链可行的集合找出来,这个集合要选出 c [ i ] c[i] c[i]条链(不可重),最后拼出 n n n。这实际上相当于是一个子集卷积。
  • 因为总数就是n,所以贡献到 2 n − 1 2^n-1 2n1位置一定不会有重复的点,所以直接FWT即可。预先处理点值,递归的时候乘一乘,用简单的DP做链的状况。
  • 求出每一种01串的方案数,就可以反演回去了。因为原先的形式相当于是And卷积的形式,所以IFWT回去就好(也可以理解为高维前缀和的逆运算)。
  • 一道灵活运用FWT子集转移的题目
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 17
using namespace std;

int n,q,i,j,k,a[maxn][maxn],cnt[1<<maxn],d[maxn+1][1<<maxn],c[maxn+1];
ll f[1<<maxn][maxn],g[maxn+1][1<<maxn];

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

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

ll F[maxn][1<<maxn],G[1<<maxn];
void cover(int s,ll sum,int S){
	if (s==n){
		G[S]+=sum;return;
	}
	for(int i=1;i<=n;i++) if (c[i])
		c[i]--,cover(s+i,sum,(S<<i)|((1<<i-1)-1)),c[i]++;
}

void dg(int i,int res){
	if (res&&(res<i||i>n)) return;
	if (i>n||res==0){
		for(int j=i;j<=n;j++) c[j]=0;
		ll sum=0;
		for(int j=0;j<1<<n;j++)
			sum+=F[i-1][j]*(((n-cnt[j])&1)?-1:1);
		cover(0,sum,0);
		return;
	}
	for(int j=0;i*j<=res;j++){
		c[i]=j,memcpy(F[i],F[i-1],sizeof(F[i]));
		dg(i+1,res-i*j);
		for(int k=0;k<1<<n;k++) F[i-1][k]=F[i-1][k]*g[i][k];
	}
}

int main(){
//	freopen("s1mple.in","r",stdin);
//	freopen("s1mple.out","w",stdout);
	scanf("%d",&n); char ch=getchar();
	for(i=0;i<n;i++){
		while (ch!='0'&&ch!='1') ch=getchar();
		for(j=0;j<n;j++) a[i][j]=ch-'0',ch=getchar();
	}
	for(i=0;i<n;i++) f[1<<i][i]=1;
	for(int S=0;S<1<<n;S++) for(i=0;i<n;i++) if (f[S][i])
		for(j=0;j<n;j++) if (!(S>>j&1)&&a[i][j])
			f[S|(1<<j)][j]+=f[S][i];
	for(i=1;i<1<<n;i++) {
		cnt[i]=cnt[i>>1]+(i&1);
		d[cnt[i]][++d[cnt[i]][0]]=i;
	}
	for(i=1;i<=n;i++) {
		for(j=1;j<=d[i][0];j++) {
			int S=d[i][j];
			for(k=0;k<n;k++) if (S>>k&1)
				g[i][S]+=f[S][k];
		}
		fwt(g[i]);
	}
	for(i=0;i<1<<n;i++) F[0][i]=1;
	dg(1,n);
	ifwt(G);
	scanf("%d",&q),ch=getchar();
	while (q--){
		while (ch!='0'&&ch!='1') ch=getchar();
		int P=0; for(i=1;i<n;i++) P=P<<1|(ch-'0'),ch=getchar();
		printf("%lld\n",G[P]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值