【Unknown Source】异或和

Descripition

给定 R,n,求在 [0,R) 中选择 n 个互不相同的数字满足其异或和为 0 的方案数 mod998244353 的结果

R 非常大,给出其二进制下表示中的所有 1 的位置:共 K 个从低到高给出的 ai,其中 maxai1145141919810

n,K114514

Solution

先求有多少选择数的排列,最后除掉阶乘

考虑一类集合划分容斥,将所有元素分成若干集合,每个集合内部数字相等,对这些集合划分的方式赋以一定的系数使得合法者贡献 1 次,非法者贡献 0

这个模型和 ABC236Ex 是一样的,将所有元素互不相等的关系转化如下

由于排列中任意两个元素都可能相等,所以可以将元素自身看做一个点,元素之间的相等关系看做边

取出全部边集的一个子集 E,强制每个联通块中的元素数值相等,联通块之间不强制不相等,并要求所有元素的 和为 0 ,贡献系数是 (1)|E|,方案数 f(E) 是给元素分配数值使其满足相等关系且异或和为 0 的方案数

关于该容斥系数的正确性

如果图上不存在边(也就是所有元素都相等,是一个合法方案)那么选择边表子集的方案数为 1

否则有元素相等,不合法,需要证明其所有方案的系数之和为 0,可以找到图上存在的一条边 e,对于所有其它边的选取方案,选择 e 和不选择 e 会导致奇偶性不同,系数正负 1 消去了



注意到 f(E) 的值只和 E 中包含的奇数联通块的个数有关,那么所有的 f(E) 可以归为对于 k[0,n] 计算选择 k 个不要求不等且都 [0,R) 的数字使得异或和为 0

考虑对于单个 k,枚举这些数字和 RLCP 以及这个位置上有多少个数字选了 1

如果这位选择了 1,后面选数字的方案是后缀位上权值和 s,选择 0 的数字中有一个用来最后进行抵消,另外的数字选择方案是当前位的权值 b,方案数即:

i=0k1[2|i](ki)sibki1=12b((bs)k+(b+s)ksk(1+(1)k))

暴力求解 Θ(nK) 个这样的式子就前功尽弃,由于每位的 b,s 是固定的,变化的只有 k,尝试计算 i0xij=0K1sji2bj=j=0K1sji2bj(1x)

使用分治乘法进行通分得到完整多项式,对 (b±s)k 做一样的通分即可

注意 k 是奇数时 LCP 只能是 0,直接根据上面的公式来计算即可


要求出来答案,避不开的是对于每个 k[0,n] 求出来有几个 f(E) 满足有 k 个联通块大小为奇数

先考察所有选择边将 x 个点联通的方式 {E} 中,(1)|E| 之和 h(x),注意到 h(1)=1

x>1 时,用全部方案减去不连通方案,全部方案的权值和上面提及了是 0 ,不连通则枚举 1 所在联通块的大小

但是在外部点数 >1 的情况下给它们任意连边的所有方案权值和是 0

只有一个点时选点有 x1 种方案,每个点对应的权值之和都是 h(x1) ,所以有 h(x)=(x1)h(x1) ,得到 h(n)=(1)n1(n1)!

那么设 F(x) 为选出一个大小为奇数的集合的 EGF,同时设 G(x) 为所有偶数大小集合的 EGF ,使用上面所说的容斥系数可以得到下面的式子

F(x)=i0x2i+12i+1=12(ln(1+x)ln(1x))G(x)=exp(Ri1x2i2i)=(1x2)R/2

直接使用 F1(x) 表示 F(x) 的复合逆,根据复合逆定义有:

2F(x)=ln(1+x1x)e2F(x)=1+x1xe2x=1+F1(x)1F1(x)F1(x)=e2x1e2x+1

H(x)=G(F1(x)),有 H(F(x))=G(x)

套用扩展拉格朗日反演公式:

[xn]H(F(x))=[xn1]H(x)(xF1(x))n

[xn]G1yF=[xn]H(F)1yF=[xn1]1n(H(x)1yx)xnF1(x)n=[xn1]1nH(x)(1yx)+yH(x)(1yx)2xnF1(x)n=[xn1]1nH(x)+y(H(x)H(x)x)(1yx)2xnF1(x)n

由于所需为一个关于 y 的多项式且每项都有 xn,那么需要继续进行和式变换

[xn1]xn(H(x)+y(H(x)xH(x)))nF1(x)ni0(i+1)(yx)i=[xn1]xnH(x)nF1(x)n+[xn1](xnH(x)nF1(x)ni1(i+1)(yx)i+xn(H(x)xH(x))nF1(x)ni1iyixi1)=[xn1]xnH(x)nF1(x)n+i1yi[xn1](xixnH(x)+ixi1xnH(x)nF1(x)n)

求解负指数次方是很困难的,但是注意到 F1(x) 的常数项为 0 所以可以上下同时除以 xn

一步一步求复杂度就是 Θ(nlogn)

注意到在进行 F 的乘方运算时每个 F 之间没有顺序,所以要除掉 i!


最后统计答案直接将两个部分得到的权值对位乘起来相加即可

卡常注意在分治乘法时不要使用 Poly::Mul 而是展开 NTT

Code

#pragma GCC target("avx")
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
const int N=6e5+10;
int fac[N],ifac[N],inv[N];
int n,K,R;
namespace PART1{
	inline poly get_revF(){
		int pw2=1;
		vector<int> vec1,vec2;
		vec1.resize(n+1); vec2.resize(n+1);
		for(int i=0;i<=n;++i){
			vec1[i]=vec2[i]=mul(ifac[i],pw2);
			ckadd(pw2,pw2);
		}
		ckadd(vec1[0],1); ckdel(vec2[0],1);
		vec1=Inv(vec1,n+1);
		vec2=Mul(vec2,vec1);
		vec2.resize(n+1);
		return vec2;
	}
	vector<int> revF;
	inline poly get_H(){
		vector<int> vec=revF;
		vec=Mul(vec,vec);
		vec.resize(n+1);
		for(auto &t:vec) t=del(0,t);
		ckadd(vec[0],1);
		return qpow(vec,mul(R,inv[2]));
	}
	poly main(){
		revF=get_revF();
		vector<int> H=get_H();
		revF.erase(revF.begin());
		revF=qpow(revF,n);
		revF.resize(n+1);
		revF=Inv(revF,n+1);
		vector<int> dH=deriv(H);
		poly M=Mul(H,revF);
		poly dM=Mul(dH,revF);
		M.resize(n+1); dM.resize(n);
		vector<int> ans={dM[n-1]};
		ans.resize(n+1);
		for(int i=1;i<=n;++i){
			int val1=i==n?0:dM[n-1-i];
			int val2=mul(i,M[n-i]);
			ans[i]=add(val1,val2);
			ckmul(ans[i],ifac[i]);
		}
		for(auto &t:ans) ckmul(t,inv[n]);
		return ans;
	}
}
namespace PART2{
	vector<int> A,T;
	inline pair<vector<int>,vector<int> >solve(int l,int r){
		if(l==r)return {{1},{T[l],del(0,mul(A[l],T[l]))}};
		int mid=(l+r)>>1;
		pair<poly,poly> a=solve(l,mid),b=solve(mid+1,r);
		pair<poly,poly> c;
		int lim=1; while(lim<=a.sec.size()+b.sec.size()) lim<<=1;
		NTT(a.sec,lim,1);
		NTT(b.sec,lim,1);
		NTT(a.fir,lim,1);
		NTT(b.fir,lim,1);
		c.fir.resize(lim);
		c.sec.resize(lim);
		for(int i=0;i<lim;++i){
			c.fir[i]=add(mul(a.fir[i],b.sec[i]),mul(b.fir[i],a.sec[i]));
			c.sec[i]=mul(a.sec[i],b.sec[i]);
		}
		NTT(c.fir,lim,-1); NTT(c.sec,lim,-1);
		while(c.fir.size()>1&&!c.fir.back()) c.fir.pop_back();
		while(c.sec.size()>1&&!c.sec.back()) c.sec.pop_back();
		return c;
	}
	int t[N],s[N];
	poly main(){
		for(int i=1;i<=K;++i) s[i]=add(s[i-1],t[i]=ksm(2,a[i]%(mod-1)));
		A.resize(K); T.resize(K);
		for(int i=1;i<=K;++i){
			A[i-1]=del(t[i],s[i-1]);
			T[i-1]=add(t[i],t[i]);
		}
		pair<poly,poly> tmp=solve(0,K-1);
		tmp.sec.resize(n+1);
		vector<int> vec1=Mul(tmp.fir,Inv(tmp.sec,n+1));;

		for(int i=1;i<=K;++i){
			A[i-1]=add(t[i],s[i-1]);
			T[i-1]=add(t[i],t[i]);
		}
		tmp=solve(0,K-1);
		tmp.sec.resize(n+1);
		vector<int> vec2=Mul(tmp.fir,Inv(tmp.sec,n+1));;

		for(int i=1;i<=K;++i){
			A[i-1]=s[i-1];
			T[i-1]=add(t[i],t[i]);
		}
		tmp=solve(0,K-1);
		tmp.sec.resize(n+1);
		vector<int> vec3=Mul(tmp.fir,Inv(tmp.sec,n+1));;
		vec1.resize(n+1); vec2.resize(n+1); vec3.resize(n+1);
		vector<int> ans=Plus(vec2,vec1);
		for(int i=0;i<=n;++i){
			ckdel(ans[i],vec3[i]);
			if(i&1) ckadd(ans[i],vec3[i]);
			else ckdel(ans[i],vec3[i]); 
		}
		for(int i=1;i<=n;i+=2){
			int S=s[K-1],T=t[K];
			int coef=ksm(add(T,T),mod-2);
			int val=add(ksm(del(T,S),i),ksm(add(T,S),i));
			if(!(i&1)) ckadd(val,mul(2,ksm(S,i))); 
			ans[i]=mul(coef,val);
		}
		ans[0]=1;
		return ans;
	}
}
signed main(){
	freopen("xor.in","r",stdin); freopen("xor.out","w",stdout);
	n=6e5; fac[0]=inv[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=ksm(fac[n],mod-2);
	for(int i=n;i>=1;--i) ifac[i-1]=mul(ifac[i],i),inv[i]=mul(ifac[i],fac[i-1]);
	n=read(); K=read();
	for(int i=1;i<=K;++i)a[i]=read<ll>(),ckadd(R,ksm(2,a[i]%(mod-1)));
	vector<int> res1=PART1::main();
	vector<int> res2=PART2::main();
	int ans=0;
	for(int i=0;i<=n;++i) ckadd(ans,mul(res1[i],res2[i]));
	print(ans);
	return 0;
}
posted @   没学完四大礼包不改名  阅读(148)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示