6709. 【2020.06.08省选模拟】排列

题目


正解

显然先要将“恰好”转成“至少”,算出“至少”之后直接反演回去。

比赛的时候建出了个和题解不一样的模型,搞了个二维生成函数。
最后想出的方法需要依赖多点插值,普通的多点插值常数大又不好写,所以没有去写。
后来经过DYP提醒,直接插单位根不就好了吗!!!
于是时间复杂度应该是 O ( n lg ⁡ n ) O(n\lg n) O(nlgn)

再详细点讲题解做法。
考虑某个点 i i i i − m i-m im i + m i+m i+m争抢,画出一个二分图, i i i在右边, i − m i-m im i + m i+m i+m在左边。通过争抢关系建出图之后,可以发现形成了若干条链。
于是模型就转化成了,在一条长度为 L L L的链上,选择任意条点不相交的边。
假如选 i i i条边,方案数显然为 C n − i i C_{n-i}^i Cnii
于是每条链都可以写出个生成函数,将所有链卷积起来即可。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 262144
#define ll long long
#define mo 998244353
ll qpow(ll x,int y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int nN;
int re[N];
void dft(int A[],int flag){
	for (int i=0;i<nN;++i)
		if (i<re[i])
			swap(A[i],A[re[i]]);
	for (int i=1;i<nN;i<<=1){
		ll wn=qpow(3,flag==1?(mo-1)/(2*i):mo-1-(mo-1)/(2*i));
		for (int j=0;j<nN;j+=i<<1){
			ll wnk=1;
			for (int k=j;k<j+i;++k,wnk=wnk*wn%mo){
				ll x=A[k],y=wnk*A[k+i]%mo;
				A[k]=(x+y)%mo;
				A[k+i]=(x-y+mo)%mo;
			}
		}
	}
	if (flag==-1){
		ll invn=qpow(nN);
		for (int i=0;i<nN;++i)
			A[i]=A[i]*invn%mo;
	}
}
int n,m;
ll fac[N],ifac[N];
ll C(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}
int f0[N],f1[N],f[N];
void getf(int n,int f[]){
	for (int i=0;i<=n-i;++i)
		f[i]=C(n-i,i);
}
int a[N],b[N],g[N];
int main(){
//	freopen("in.txt","r",stdin);
	freopen("perm.in","r",stdin);
	freopen("perm.out","w",stdout);
	scanf("%d%d",&n,&m);
	int bit=0;
	for (nN=1;nN<=2*n;nN<<=1,++bit);
	re[0]=0;
	for (int i=1;i<nN;++i)
		re[i]=re[i>>1]>>1|(i&1)<<bit-1;
	fac[0]=1;
	for (int i=1;i<=n;++i)
		fac[i]=fac[i-1]*i%mo;
	ifac[n]=qpow(fac[n]);
	for (int i=n-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
	getf(n/m,f0),getf(n/m+1,f1);
	dft(f0,1),dft(f1,1);
	for (int i=0;i<nN;++i)
		f[i]=qpow(f0[i],(m-n%m)*2)*qpow(f1[i],(n%m)*2)%mo;
	dft(f,-1); 
	for (int i=0;i<=n;++i)
		f[i]=f[i]*fac[n-i]%mo;
	for (int i=0;i<=n;++i){
		a[i]=(ll)f[i]*fac[i]%mo;
		b[i]=(ll)ifac[n-i]*(n-i&1?mo-1:1)%mo;
	}
	dft(a,1),dft(b,1);
	for (int i=0;i<nN;++i)
		g[i]=(ll)a[i]*b[i]%mo;
	dft(g,-1);
	for (int i=0;i<=n;++i)
		printf("%lld\n",(ll)g[i+n]*ifac[i]%mo);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值