题解 ABL F - Heights and Pairs

一点闲话:我太菜了,这题考场上都切不了,果然还是需要学习呢。

题意简述

给定长度为 \(2n\) 的序列,请将其分为 \(n\) 组,每组两个数,并且这两个数的值不一样。

题目分析

比较显然的容斥,设 \(A_i\) 表示第 \(i\) 组的值两个不同的方案,那么:

\[\left | \bigcap_{1\le i\le n} A_i \right | =\sum_{S\subset [n]}(-1)^{\left |S\right |}\left| \bigcap_{i\in S}\overline{A_i}\right | \]

把值相同的分开考虑计算贡献,最后再用 NTT 乘起来,设 \(f(x)\) 表示长度为 \(2x\) 的序列两两任意分组的方案数,那么有递推式:

\[f(x)=f(x-1)\times (2x-1) \]

假设当前考虑的值的数量是 \(len\) ,那么当前值对 \(S\) 的大小的贡献为 \(x\) 时,方案数是 \({len \choose 2x}f(x)\) ,乘起来的过程用分治 NTT 实现即可,复杂度是 \(\mathcal O(n\log_2^2n)\) ,唯一需要注意的就是在算答案的时候当 \(S\) 大小为 \(x\) 时还要乘上 \(f(n-x)\) ,因为还有 \(n-x\) 组没有分配完。

参考代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=50005,mod=998244353;
int mo(const int x){
	return x>=mod?x-mod:x;
}
int power(int a,int x){
	int re=1;
	while(x){
		if(x&1)re=1ll*re*a%mod;
		a=1ll*a*a%mod,x>>=1;
	}
	return re;
}
int rev[maxn*4],N;
void NTT(int*F,int rv){
	for(int i=0;i<N;++i)if(rev[i]<i)
		F[rev[i]]^=F[i]^=F[rev[i]]^=F[i];
	for(int mid=1;mid<N;mid<<=1){
		int len=mid<<1,gn=power(3,(mod-1)/len);
		for(int i=0;i<N;i+=len){
			for(int j=0,g=1;j<mid;++j,g=1ll*g*gn%mod){
				int l=i+j,r=l+mid;
				int L=F[l],R=1ll*F[r]*g%mod;
				F[l]=mo(L+R),F[r]=mo(mod-R+L);
			}
		}
	}
	if(!rv)return;reverse(F+1,F+N);int tmp=power(N,mod-2);
	for(int i=0;i<N;++i)F[i]=1ll*tmp*F[i]%mod;
}
int F[maxn*4],G[maxn*4],S[maxn*2],L[maxn*2],R[maxn*2];
void solve(int l,int r){
	if(l==r)return;int mid=(l+r)>>1,o=mid+1;
	solve(l,mid);solve(o,r);
	int mx=R[l]-L[l]+R[o]-L[o]-1,cn=-1;N=1;
	while(N<mx)N<<=1,++cn;
	for(int i=1;i<N;++i)
		rev[i]=(rev[i>>1]>>1)|((i&1)<<cn);
	int cf=0,cg=0;
	for(int i=L[l];i!=R[l];++i)F[cf++]=S[i];
	for(int i=L[o];i!=R[o];++i)G[cg++]=S[i];
	while(cf!=N)F[cf++]=0;while(cg!=N)G[cg++]=0;
	NTT(F,0);NTT(G,0);
	for(int i=0;i<N;++i)F[i]=1ll*F[i]*G[i]%mod;
	NTT(F,1);
	for(int i=0;i<mx;++i)S[L[l]+i]=F[i];
	R[l]=L[l]+mx;
}
int GO[maxn],iac[maxn*2],fac[maxn*2];
int binom(int n,int m){
	return 1ll*iac[m]*iac[n-m]%mod*fac[n]%mod;
}
int a[maxn*2];
int main(){
	int n;read(n);
	GO[0]=1;
	for(int i=1;i<=n;++i)
		GO[i]=1ll*GO[i-1]*(i*2-1)%mod;
	iac[0]=iac[1]=fac[0]=fac[1]=1;
	for(int i=2;i<=n*2;++i)
		iac[i]=1ll*(mod-mod/i)*iac[mod%i]%mod;
	for(int i=2;i<=n*2;++i)
		iac[i]=1ll*iac[i-1]*iac[i]%mod,fac[i]=1ll*fac[i-1]*i%mod;
	for(int i=1;i<=n*2;++i)read(a[i]);
	sort(a+1,a+n*2+1);int cnt=0,num=0;
	for(int l=1,r=l;l<=n*2;l=r=r+1){
		while(r+1<=n*2&&a[r+1]==a[l])++r;
		int len=r-l+1;++num;L[num]=cnt;
		for(int x=0;x*2<=len;++x)
			S[cnt++]=1ll*GO[x]*binom(len,x*2)%mod;
		R[num]=cnt;
	}
	solve(1,num);
	int mx=R[1]-L[1],delta=L[1],ans=0;
	for(int i=0;i<mx;++i){
		int tmp=1ll*GO[n-i]*(S+delta)[i]%mod;
		if(i&1)tmp=mo(mod-tmp);
		ans=mo(ans+tmp);
	}
	write(ans),pc('\n');
	return 0;
}

posted @ 2020-09-27 09:31  xiaolilsq  阅读(425)  评论(0编辑  收藏  举报