LOJ 3395 集训队作业 Yet Another Permutation Problem 题解 (生成函数技巧)

题目链接

原排列进行k次操作后,一定会剩下一个长度至少为nk的上升区间。观察发现,一个排列能用k次操作得到的充要条件是:其中的最长上升区间长度nk

我们枚举k,对每个k计算答案。最长上升区间长度nk的排列个数不太好算,我们可以算出最长上升区间长度nk1的排列个数,然后用总数减。为了方便,接下来的k表示上面的nk1

我们先把序列划分成一些段,要求每一段都是单调递增,且每段长度k,求总方案数(我知道会重复计数,但你先别急)。这是一个经典的生成函数计数题。令fi表示i个不同元素排成上升序列的方案数,显然fi=1(ik),为了方便使用生成函数,我们令f0=0。令F(x)为f的指数型生成函数,G(x)为f的普通生成函数。F(x)=i=1kxii!G(x)=i=1kxi。根据EGF的组合意义,这个问题的答案为:([xn]i=0Fi(x))n!,其中[xn]表示这个多项式的n次项系数。

再来看看这个问题与原问题的关系。原问题中每个排列在这个问题中都被计数了多次。具体来说,对于一个排列,我们先把他划分成若干极长的上升区间,令它们的长度从左到右为l1lm。这个排列在这个问题中其实被计数了这么多次:i=1m([xli]j=0Gj(x)),从组合意义的角度这个式子也非常好理解。那么我们希望这个排列被计数多少次呢?其实是i=1m[lik]次。如果我们能适当地修改f数组,使得对于任意l[xl]i=0Gi(x)=[lk],那么只要按照这个问题的流程跑一遍,就得到原问题的答案了。这其实是可以做到的。

(1)[xl]i=0Gi(x)=[lk](2)i=0Gi(x)=i=0kxi(3)11G(x)=1xk+11x(4)G(x)=xxk+11xk+1

如果能求出G(x),也就确定了f,也就能求出F(x)从而求出答案了。这里似乎可以用多项式求逆,但是模数不是998244353所以会有问题。写任意模NTT的话常数又似乎太大了。我们来观察一下暴力多项式求逆的过程,看看能不能投机取巧:

(5)ba(6)b0=1a0(7)bi=j=0i1bjaija0

为了避免混淆,重申一下"多项式求逆"的含义:对于一个多项式A(x),求出一个多项式B(x),使得A,B乘积的常数项为1,且1次项到n次项的系数都为0,更高次项的系数不管。

观察上面的暴力,发现其复杂度与a中不为0的项的数量有关(求那个sigma的时候可以只枚举a中不为0的项)。在求G(x)的过程中我们要给1xk+1求逆,打表(划掉)手画(划掉)观察一下发现1xk+1的逆中只有大约nk项不为0,所以G(x),F(x)中不为0的项数也差不多是这个数量级。这部分的复杂度是O(n2)(算上之前枚举k)。

然后是求出F(x)之后计算答案。我们需要计算i=0Fi(x)=11F(x)这个多项式。对1F(x)求逆时,由于其中非0项的个数是nk级别,求逆的复杂度是O(n2k)的。算上枚举k,总复杂度是O(n2logn)的。

点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
using namespace std;
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
LL n,MOD,fac[1010],inv[1010];
LL qpow(LL x,LL a)
{
LL res=x,ret=1;
while(a>0)
{
if(a&1) (ret*=res)%=MOD;
a>>=1;
(res*=res)%=MOD;
}
return ret;
}
vector <LL> getInv(vector <LL> A)
{
vector <pii> v;
rep(i,A.size()) if(A[i]!=0) v.pb(mpr(i,A[i]));
vector <LL> ret;
ret.pb(qpow(A[0],MOD-2));
repn(i,A.size()-1)
{
LL hv=0;
rep(j,v.size())
{
if(v[j].fi>i) break;
if(i-v[j].fi<ret.size()) (hv+=v[j].se*ret[i-v[j].fi])%=MOD;
}
hv=(MOD-hv)%MOD;
ret.pb(hv*ret[0]%MOD);
}
return ret;
}
vector <LL> polyMul(vector <LL> A,LL mns)
{
vector <LL> ret;ret.pb(0);rep(i,A.size()-1) ret.pb(A[i]);
LL mul=MOD-1;
rep(i,A.size()) if(i+mns<ret.size()) (ret[i+mns]+=A[i]*mul)%=MOD;
return ret;
}
LL calc(LL k)
{
if(k==0) return 0;
vector <LL> A;
A.pb(1);repn(i,k) A.pb(0);A.pb(MOD-1);while(A.size()<n+1) A.pb(0);
vector <LL> B=getInv(A);
vector <LL> F=polyMul(B,k+1);
rep(i,F.size()) (F[i]*=inv[i])%=MOD;
rep(i,F.size()) F[i]=(MOD-F[i])%MOD;
(++F[0])%=MOD;
F=getInv(F);
LL ret=F[n]*fac[n]%MOD;
return ret;
}
int main()
{
fileio();
cin>>n>>MOD;
fac[0]=1;repn(i,1005) fac[i]=fac[i-1]*i%MOD;
rep(i,1003) inv[i]=qpow(fac[i],MOD-2);
rep(nk,n)
{
LL k=n-nk-1;
//答案=总数-所有上升子段的长度都<=k的排列数量
LL ans=(fac[n]-calc(k)+MOD)%MOD;
printf("%lld\n",ans);
}
termin();
}
posted @   LegendStane  阅读(112)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示