[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;
}
标签:10,return,GDKOI2023,int,Subtask,iv,1LL,错排
From: https://www.cnblogs.com/mekoszc/p/17999380