嘘月
⚠警告:题解非常摧毁本题的思考体验,作者强烈建议您先思考完题再来看题解
题目简述
对于一个排列,我们维护一个指针
对于所有排列,求可以使得
构建映射
首先,明确一点。我们总是会标记比上一次标记的大的最小的那个数,如果没有比就寄了。
为了方便直观理解,我们把
我们考虑
先来考虑
再来考虑
- 对于原序列的前
m 个积木,染上不同的颜色。 - 若我们搭了一个颜色的积木
c 后,指针新移动到的位置对应的积木也染成c 。 - 为每个颜色确定标号,倒数第
m 个积木的颜色编号为1 ,倒数第m-1 个染成2 ,以此类推,最后一个积木染色成m 。
右边是染色后的结果。蓝色为
我们染色的目的是展示这样一个事实:
- 一个数列对应的
m=2 的塔可以唯一划分为两个m=1 的塔,并为他们确定标号。 - 两个确定好标号的
m=1 的塔也能唯一归并出一个已染色的m=2 的塔。归并的方式是,每个塔除去最后一块的部分,按照大小排序;每个塔的最后一块,按照标号排序。
然后,我们再来讨论原数列与已染色的
- 根据各个颜色第一个的数,可以求出数列的前
m 项的集合,对应了m! 种排列。这个m! 的系数我们不急着考虑,最后再来乘。 - 取出比上一个放的积木更大的积木中最小的那个,把下一个此颜色的积木放在序列尾,然后更新你手上积木的集合。
其他
我们发现这些步骤对于
开始计数
考虑
则固定
乘
至此,可以一次多项式 exp 解决
上面是搭不成功的概率,那么成功的概率就是:
至此,可以
#include<bits/stdc++.h>
#define ll long long
const int N=20005,mod=998244353;
using namespace std;
ll gsc(ll x,ll y){
ll ans=1;
for(int i=1;i<=y;i<<=1,x=x*x%mod)
if(y&i)
ans=ans*x%mod;
return ans;
}int inv(int k){return gsc(k,mod-2);}
ll jc[N],ij[N],iv[N];
void init(){
iv[0]=jc[0]=ij[0]=iv[1]=1;
for(int i=2;i<N;i++)
iv[i]=mod-(mod/i)*iv[mod%i]%mod;
for(int i=1;i<N;i++)
jc[i]=jc[i-1]*i%mod,ij[i]=ij[i-1]*iv[i]%mod;
}
int n,q,vis[N],m;
ll f[N];int C[N];
int chk(int k){
return k>=mod?k-mod:k;
}
void solve(){
cin>>n>>q,m=n/2;
while(q--){
int x;cin>>x;
vis[x]=1;
}
init();
C[0]=1;
for(int i=1;i<=m;i++){
ll pwi=gsc(i,n-1),p=iv[i];
for(int j=0;j<i;j++){
f[i]+=((i+j+1)&1?-1:1)*pwi*C[j]%mod*ij[n-1-j];
f[i]%=mod;
pwi=pwi*p%mod;
}
f[i]=(f[i]%mod+mod)%mod;
for(int j=i;j;j--)
C[j]=chk(C[j]+C[j-1]);
}
memset(C,0,sizeof(C));
C[0]=1;
for(int i=1;i<=m;i++){
unsigned ll res=0;
for(int j=i;j;j--){
C[j]=chk(C[j]+C[j-1]);
res+=C[j]*f[j];
res%=mod;
}
res=res%mod+mod;
if(vis[i])cout<<res%mod<<'\n';
}
for(int i=m+1;i<=n;i++)
if(vis[i])cout<<1<<'\n';
}
main(){solve();}
设
设
设
那么如果我们求出了
考虑分治,设
考虑合并,有
边界为
最后用一次卷积得到每一个