[GDKOI2023]错排

[GDKOI2023 提高组] 错排

题目描述

小 X 最近学习了错排问题,于是开始思考一个关于它的变种问题:有多少个长度为 \(n\) 的排列 \(p\),满足对
\(i \le m\) 的位置满足 \(p_i > m\),且对于所有位置 \(i\) 都满足 \(p_i \ne i\)

小 X 一共想出了 \(T\) 个这样的问题,你能告诉他每个问题的答案吗?

由于答案可能过大,你只需要求出答案对 \(998244353\) 取模后的值即可。

输入格式

第一行输入一个整数 \(T\),表示询问组数。

接下来的 \(T\) 行,每行输入两个整数 $ n, m$。

输出格式

输出 \(T\) 行,每行一个整数表示答案对 \(998244353\) 取模后的值。

样例 #1

样例输入 #1

6
8 0
8 4
100 10
1000 100
10000 1000
100000 10000

样例输出 #1

14833
576
548326276
694205000
493811811
135068319

提示

对于 100% 的数据,\(0 ≤ T ≤ 2 \times 10^5\)\(0 ≤ m ≤ n ≤ 2 \times 10^5\)

本题采用子任务捆绑测试。

  • Subtask 1 (1pts):保证 \(T = 0\)
  • Subtask 2 (9pts):保证 \(T ≤ 10\)\(n, m ≤ 8\)
  • Subtask 3 (10pts):保证 \(m = 0\)
  • Subtask 4 (20pts):保证 \(n, m ≤ 5000\)
  • Subtask 5 (20pts):保证 \(T ≤ 10\)
  • Subtask 6 (40pts):无特殊性质。

这题好像有很厉害的 \(O((n+m)\sqrt n)\) 甚至 \(O(nlog^2n)\) 做法,但是我不会。
首先 \(2m>n\) 显然无解。

\(f_(i,j)\) 表示有 \(i\) 个位置,但是有 \(j\) 个位置钦定不一定要错开排的,\(i-j\) 个位置一定要错开的方案数,\(g_i\)\(i\) 个数的错排数。

最后答案就是 \(\frac{(n-m)!}{(n-2m)!}\times f(n-m,m)\)

考虑如何计算 \(f(i,j)\)。枚举有多少个数实际上是错排的,

\[f(i,j)=\sum\limits_{i=0}^{m}g_i\binom{m}{i} \]

将其写作 \([x^{n-m}]g\times(x+1)^m\),分块处理。用卷积预处理出 \(g_i\times(x+1)^{jB}\) 的式子,查询时再乘上剩下的。复杂度是 \(O(qB+\frac nBlogn)\),B 取 \(\sqrt{nlog n}\) 的时候,复杂度 \(O(n\sqrt{nlog n})\)

#include<bits/stdc++.h>
using namespace std;
const int P=998244353,g=3,ivg=(P+1)/g,N=262144,B=3000;
int f[N],TME,r[N],dp[N],n,m,pw[100][N],pf[N],iv[N],inv[N],ans,C[B][B];
int pown(int x,int y)
{
	if(!y)
		return 1;
	int t=pown(x,y>>1);
	if(y&1)
		return 1LL*t*t%P*x%P;
	return 1LL*t*t%P;
}
const int ivN=pown(N,P-2);
void ntt(int a[],int op)
{
	for(int i=0;i<N;i++)
		if(r[i]<i)
			swap(a[i],a[r[i]]);
	for(int md=1;md<N;md<<=1)
	{
		int p=pown(op? g:ivg,(P-1)/(md<<1));
		for(int i=0;i<N;i+=md<<1)
		{
			for(int j=0,pw=1;j<md;j++,pw=1LL*pw*p%P)
			{
				int x=a[i+j+md]*1LL*pw%P;
				a[i+j+md]=(a[i+j]-x+P)%P;
				(a[i+j]+=x)%=P;
			}
		}
	}
	if(!op)
		for(int i=0;i<N;i++)
			a[i]=1LL*a[i]*ivN%P;
}
inline int read()
{
	int s=0;
	char ch=getchar();
	while(ch>'9'||ch<'0')
		ch=getchar();
	while(ch<='9'&&ch>='0')
		s=s*10+ch-'0',ch=getchar();
	return s;
}
int CC(int n,int m)
{
	return 1LL*f[n]*iv[m]%P*iv[n-m]%P; 
}
int main()
{
	//freopen("P10103_21.in","r",stdin);
//	freopen("a.out","w",stdout);
	for(int i=iv[0]=f[0]=inv[1]=1;i<N;i++)
	{
		r[i]=(r[i>>1]>>1)|(i&1)*N/2;
		f[i]=1LL*f[i-1]*i%P;
		if(i^1)
		{
			dp[i]=(dp[i-1]*1LL*i+(i&1? -1:1))%P;
			inv[i]=(P-P/i)*1LL*inv[P%i]%P;
		}
		iv[i]=iv[i-1]*1LL*inv[i]%P;
	}
	dp[0]=1;
	for(int i=0;i<B;i++)
		for(int j=0;j<=i;j++)
			C[i][j]=CC(i,j);
//	printf("%d\n",C(0,0));
	pf[0]=pf[1]=1;
	ntt(dp,1);
	memcpy(pw[0],dp,sizeof(dp)); 
	ntt(pf,1);
	for(int i=0;i<N;i++)
		pf[i]=pown(pf[i],B);
	for(int i=1;i*B<N;i++)
		for(int j=0;j<N;j++)
			pw[i][j]=pw[i-1][j]*1LL*pf[j]%P;
	for(int i=0;i*B<N;i++)
		ntt(pw[i],0);
	scanf("%d",&TME);
	while(TME--)
	{
		n=read(),m=read(),ans=0;
		if(2*m>n)
		{
			puts("0");
			continue;
		}
		int x=m%B;
		for(int j=0;j<=x&&j<=n-m;j++)
			(ans+=1LL*pw[m/B][n-m-j]*C[x][j]%P)%=P;
		printf("%lld\n",1LL*ans*f[n-m]%P*iv[n-2*m]%P);
	}
	return 0;
}
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值