【LOJ3395】【2020-2021 集训队作业】Yet Another Permutation Problem(容斥,生成函数,多项式)
题目相当于让我们考虑某种排列最少能经过几次操作得到。
操作是从 \(1\sim n\) 的顺序排列中抽取若干个元素放到排列头和尾,那么假设进行了 \(l\) 次操作,\(1\sim n\) 中还会剩下至少 \(n-l\) 个元素没有被抽取,也就是说这个排列的最长连续上升段长度至少是 \(n-l\)。
也就是说经过 \(l\) 次操作能得到某个排列 \(p\) 的必要条件是 \(p\) 的最长连续上升段的长度至少是 \(n-l\)。
显然这个条件也是充要条件:假设某个排列 \(p\) 的最长连续上升段的长度大于等于 \(n-l\),那么我们可以通过至多 \(l\) 次操作就能逆推构造出操作的方法。
经过 \(l\) 次操作能得到某个排列 \(p\) 的充要条件是 \(p\) 的最长连续上升段的长度至少是 \(n-l\)。
那么题目的问题就是对于每一个 \(k=0\sim n-1\) 问有多少个排列的最长连续上升段的长度至少是 \(n-k\)。
也等价于存在某个连续上升段长度为 \(n-k\)。
反过来会好计算一点:考虑有多少个排列满足该排列的每个连续上升段都小于 \(n-k\),最后用 \(n!\) 减去即可。
以下为了方便,令 \(k\gets n-k-1\)。
我们枚举 \(T=\{[l_i,r_i]\}\) 是排列的连续上升段的一种划分方式(即将排列划分为若干个区间 \([l_i,r_i]\),并且钦定所有的 \([l_i,r_i]\) 段内数字上升,但注意 \([l_i,r_i]\) 可能并不是一个极长的连续上升段),大胆假设:
其中 \(\left(\prod\limits_{[l,r]\in T}f(r-l+1)\right)\) 是容斥系数,\(\left(\dfrac{n!}{\prod\limits_{[l,r]\in T}(r-l+1)!}\right)\) 是将 \(1\sim n\) 分配给这些连续上升段的方案数。
那么接下来,我们需要构造一个容斥系数 \(f\) 使得上式成立。
考虑 \(1\sim n\) 的一种排列 \(p\),将它划分为若干个极长的连续上升段 \(T=\{[l_i,r_i]\}\)。
考虑这种排列在式子左右两边的贡献。它在等号左边的贡献是 \([\max_{[l,r]\in T}(r-l+1)\leq k]\)。
考虑它在等号右边的贡献:对于任意的 \([l,r]\in T\),它都有可能被分为若干个小段并被统计。
我们枚举将 \([l,r]\) 划分为若干个小段 \(K_{[l,r]}=\{[l'_i,r'_i]\}\),那么 \(T\) 在等号右边的贡献是:
又发现贡献只和段长 \(r'-l'+1\) 有关,与 \(l',r'\) 无关,那么就相当于把 \(r-l+1\) 分解成若干个数的和。于是如果我们设 \(F(x)\) 为 \(f\) 的 OGF,即 \(F(x)=\sum_i f(i)x^i\),那么:
满足上式的一个充分条件是:
于是对于每一个 \(k\),可以使用多项式求逆 \(O(n\log n)\) 算出所有的 \(f\)。
然后再看看答案怎么推:
那么如果设 \(G(x)\) 为 \(f\) 的 EGF,就有:
于是求出 \(f\) 后也能用多项式求逆 \(O(n\log n)\) 求。总时间复杂度 \(O(n^2\log n)\)。
然后还有一种做法:
注意到 \(\frac{1}{1-x^{k+1}}\) 只有 \(\frac{n}{k+1}\) 项非零,乘上 \(x-x^{k+1}\) 之后也只有 \(2\frac{n}{k+1}\) 项非零,即 \(F(x)\) 只有 \(2\frac{n}{k+1}\) 非零,于是 \(G(x)\) 和 \(1-G(x)\) 都只有 \(2\frac{n}{k+1}\) 项非零,于是暴力求 \(\frac{1}{1-G(x)}\) 的复杂度为 \(O(\frac{n^2}{k})\),那么总复杂度为 \(O(\sum_{k=1}^n\frac{n^2}{k})=O(n^2\log n)\)。那么就不用多项式求逆了。
再往下手推一下的话代码可以更加简洁,但懒得推了,就这样吧:
#include<bits/stdc++.h>
#define N 1010
using namespace std;
namespace modular
{
int mod;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
inline void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;}
}using namespace modular;
inline int poww(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n;
int fac[N],ifac[N];
int F[N],GG[N],Q[N],R[N];
//GG:1-G,Q=1/GG,R=1%GG
vector<int>v;
int main()
{
n=read(),mod=read();
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);
ifac[n]=poww(fac[n],mod-2);
for(int i=n;i>=1;i--) ifac[i-1]=mul(ifac[i],i);
for(int k=n;k>=1;k--)
{
for(int i=0;i<=n;i++) F[i]=GG[i]=Q[i]=R[i]=0;
for(int i=0;i<=n;i+=k)
{
if(i+1<=n) Add(F[i+1],1);
if(i+k<=n) Dec(F[i+k],1);
}
v.clear();
Add(GG[0],1);
for(int i=0;i<=n;i++)
{
Add(GG[i],dec(0,mul(F[i],ifac[i])));
if(GG[i]) v.push_back(i);
}
R[0]=1;
for(int i=0;i<=n;i++)
{
if(R[i])
{
Q[i]=R[i];
for(int j:v)
{
if(i+j>n) break;
Dec(R[i+j],mul(Q[i],GG[j]));
}
}
}
printf("%d\n",dec(fac[n],mul(fac[n],Q[n])));
}
return 0;
}

浙公网安备 33010602011771号