【2022国赛模拟】[ZROI2204]c——FWTXOR、NTT、分治

这是原题链接,但是寄掉了

题目描述

给出 n n n 个 0 到 K − 1 K − 1 K1 之间的整数,对于每个 0 ≤ i ≤ k − 1 0 ≤ i ≤ k − 1 0ik1 求从中选 m m m 个使得异或和为 i i i 的方案数对 998244353 取模。

对于 10% 的数据, n , K ≤ 128 n, K ≤ 128 n,K128
对于 20% 的数据, n , K ≤ 2048 n, K ≤ 2048 n,K2048
对于 40% 的数据, n , K ≤ 8192 n, K ≤ 8192 n,K8192
对于另外 10% 的数据 , m = 1 m = 1 m=1
对于另外 10% 的数据, m = 2 m = 2 m=2
对于另外 20% 的数据, m ≤ 100 m ≤ 100 m100
对于 100% 的数据, 1 ≤ m ≤ n < 130000 , K ≤ 2 17 1 ≤ m ≤ n < 130000, K ≤ 2^{17} 1mn<130000,K217,保证 K K K 是 2 的正整数次幂。

题解

首先想到可以对每个元素列一个大小为 k k k 的 FWT 点值数组,然后答案的点值即为这 n n n 个点值中任选 m m m 个相乘的和,最后逆变换把点值转换回来即可,可以做到 O ( n m k ) O(nmk) O(nmk)

然后发现每个位置的点值只可能是 1 或 -1,如果我们求出每个位置有多少个1和-1,那么就有更快的做法。假设 i i i 处的 1 的数量为 c i c_i ci,-1 的数量为 d i d_i di,由于 c i + d i = n c_i+d_i=n ci+di=n,我们再用一次普通正常的 FWTXOR 求出 c i − d i c_i-d_i cidi 即可。

1 和 -1 的个数搞定了,然后发现我们要求的其实是这么个东西: [ x m ] ( 1 + x ) c i ( 1 − x ) n − c i [x^m](1+x)^{c_i}(1-x)^{n-c_i} [xm](1+x)ci(1x)nci,一个多项式上的某一位的系数。

剩下的就比较技巧了。考虑对 0 ∼ n 0\sim n 0n 的每一种 c i c_i ci 求出 [ x m ] ( 1 + x ) c i ( 1 − x ) n − c i [x^m](1+x)^{c_i}(1-x)^{n-c_i} [xm](1+x)ci(1x)nci 的值,我们可以对 0 ∼ n 0\sim n 0n 分治往下递归来解决这个问题。

假设递归到了 [ l , r ] [l,r] [l,r] 这个区间,那么当前计算出的多项式是 ( 1 + x ) l ( 1 − x ) n − r (1+x)^l(1-x)^{n-r} (1+x)l(1x)nr,我们需要对其乘上 ( 1 − x ) r − m i d (1-x)^{r-mid} (1x)rmid 来转移到左区间 [ l , m i d ] [l,mid] [l,mid]

朴素的多项式乘法显然是 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn) 的,因为每次计算的多项式的项数是 O ( n ) O(n) O(n) 级别。但是由于我们只求 x m x^m xm 这一项的系数,所以只保留有用的项即可。举例子,假如我们递归到了区间 [ l , r ] [l,r] [l,r],那么往下递归最多会再乘上 r − l r-l rl ( 1 + x ) (1+x) (1+x) ( 1 − x ) (1-x) (1x),这个时候次数小于 m − ( r − l ) m-(r-l) m(rl) 或大于 m m m 的项肯定不会被卷积进答案里,所以多项式最多只用保留 r − l + 1 r-l+1 rl+1 项。这个时候多项式长度等于区间长度,故复杂度是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

代码

这份代码敲大常的~

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=(1<<19)+5;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
const ll MOD=998244353,iv2=(MOD+1)>>1;
ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
ll fac[MAXN<<1],inv[MAXN<<1];
ll init(int n){
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=ksm(fac[n],MOD-2,MOD);
	for(int i=n-1;i>1;i--)inv[i]=inv[i+1]*(i+1)%MOD;
	return 114514;
}
int cbddl=init(MAXN);
ll C(int n,int m){
	if(m>n||m<0)return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
#define g 3ll
int rev[MAXN];
ll omg[MAXN];
int NTT(ll*a,int N,int inv){
	int bit=1,n=N;
	while((1<<bit)<n)bit++;
	n=(1<<bit);
	for(int i=0;i<n;i++){
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		if(i<rev[i])swap(a[i],a[rev[i]]);
	}ll x,y,tmp;omg[0]=1;
	for(int m=1,mi=(MOD-1)>>1;m<n;m<<=1,mi>>=1){
		tmp=ksm(g,inv>0?mi:MOD-1-mi,MOD);
		for(int i=1;i<m;i++)omg[i]=omg[i-1]*tmp%MOD;
		for(int i=0,om=0;i<n;i+=(m<<1),om=0)
			for(int j=i;j<i+m;j++,om++)
				x=a[j],y=a[j+m]*omg[om]%MOD,a[j]=(x+y)%MOD,a[j+m]=(x+MOD-y)%MOD;
	}if(inv<0)for(int i=0,iv=ksm(n,MOD-2,MOD);i<n;i++)(a[i]*=iv)%=MOD;
	return n;
}
#undef g
void FWTXOR(ll*a,int n,int inv){
	ll cg=inv>0?1:iv2,x=2333;
	for(int k=1;k<n;k<<=1)
		for(int i=0;i<n;i+=(k<<1))
			for(int j=i;j<i+k;j++)
				x=a[j],a[j]=(x+a[j+k])*cg%MOD,a[j+k]=(x+MOD-a[j+k])*cg%MOD;
}

int n,m,k;
ll a[MAXN],f[MAXN];
vector<int>qu[MAXN];
void solve(int l,int r,ll*f,int m){
	if(l==r){
		for(int x:qu[l])a[x]=f[m];
		return;
	}int mid=(l+r)>>1,len=min(m,r-l+1);
	for(int i=0;i<=len;i++)f[i]=f[i+m-len];
	for(int i=len+1;i<=m;i++)f[i]=0;
	int N=NTT(f,(len+1)<<1,1);
	ll g[N+5];
	memset(g,0,sizeof(g));
	for(int i=0;i<=min(len,r-mid);i++)g[i]=((i&1)?(MOD-C(r-mid,i)):C(r-mid,i));
	NTT(g,N,1);
	for(int i=0;i<N;i++)(g[i]*=f[i])%=MOD;
	NTT(g,N,-1);
	for(int i=len+1;i<N;i++)g[i]=0;
	solve(l,mid,g,len);
	memset(g,0,sizeof(g));
	for(int i=0;i<=min(len,mid+1-l);i++)g[i]=C(mid+1-l,i);
	NTT(g,N,1);
	for(int i=0;i<N;i++)(g[i]*=f[i])%=MOD;
	NTT(g,N,-1);
	for(int i=len+1;i<N;i++)g[i]=0;
	solve(mid+1,r,g,len);
}
int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++)a[read()]++;
	FWTXOR(a,k,1);
	for(int i=0;i<k;i++)qu[(a[i]+n)%MOD>>1].push_back(i);
	f[0]=1,solve(0,n,f,m);
	FWTXOR(a,k,-1);
	for(int i=0;i<k;i++)print(a[i],i<k-1?' ':'\n');
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值