题目
https://gmoj.net/senior/#main/show/6830
题解
发现原题所求等价于现在我们可以把一个序列的头或尾扔进序列中的任意一个位置上,对于每一个 [ 0 , n − 1 ] [0,n-1] [0,n−1]中的 k k k,求有多少个序列在进行不超过k次操作后能够变成排列 1 , 2 , 3 , ⋯ n − 1 , n 1,2,3,\cdots n-1,n 1,2,3,⋯n−1,n。
容易发现这样的序列要满足一个条件:最长连续上升子序列的长度大于等于 n − k n-k n−k。
正难则反,令 p = n − k p=n-k p=n−k,现在要求的是有多少个 n n n的排列使得其最长连续上升子序列长度小于 p p p,也就是所有连续上升子序列的长度都小于 p p p。
那么现在有一个很简单的想法:
设
f
i
f_i
fi表示当前已经处理完序列的前
i
i
i个位置的序列数。那么
f
i
=
∑
j
=
i
−
p
+
1
i
−
1
f
j
⋅
(
n
−
j
i
−
j
)
f_i=\sum_{j=i-p+1}^{i-1}f_{j}\cdot \tbinom{n-j}{i-j}
fi=j=i−p+1∑i−1fj⋅(i−jn−j)
但是一个长度为
l
e
n
len
len的连续上升子序列可能会被算了多次(比如序列
2
,
3
,
5
,
6
,
9
2,3,5,6,9
2,3,5,6,9会被
2
,
3
2,3
2,3和
5
,
6
,
9
5,6,9
5,6,9重复计算),假设长度为
l
e
n
len
len的序列被计算了
h
l
e
n
h_{len}
hlen次,
l
e
n
len
len有一种划分方案使得
l
e
n
=
∑
a
i
len=\sum a_i
len=∑ai,那么
h
l
e
n
=
∑
每一种划分方案
a
∏
h
a
i
h_{len}=\sum_{\text{每一种划分方案}a}\prod h_{a_i}
hlen=每一种划分方案a∑∏hai
因此我们还需要在
f
j
⋅
(
n
−
j
i
−
j
)
f_j\cdot \tbinom{n-j}{i-j}
fj⋅(i−jn−j)后乘上一个容斥系数
g
i
−
j
g_{i-j}
gi−j。
现在就要现办法使得乘上这个容斥系数后,每一个长度的序列只被计算一次。
现在要解出
g
g
g,于是把
g
g
g写成生成函数的形式:令
G
(
x
)
=
∑
i
=
0
n
g
i
x
i
G(x)=\sum_{i=0}^n g_ix^i
G(x)=∑i=0ngixi。
那么有
∑
i
=
0
+
∞
G
(
x
)
i
=
∑
i
=
0
p
−
1
x
i
\sum_{i=0}^{+\infty}G(x)^i=\sum_{i=0}^{p-1}x^i
i=0∑+∞G(x)i=i=0∑p−1xi
这里为什么会有一段
∑
i
=
0
+
∞
\sum_{i=0}^{+\infty}
∑i=0+∞呢?
首先,
G
(
x
)
i
G(x)^i
G(x)i的意义是把段分为
i
i
i块。而如果用一个非无限大的数代替
+
∞
+\infty
+∞的话,会出现一个无法消去的最高次项。因此是这样。
代入等比数列求和公式,得
−
1
G
(
x
)
−
1
=
x
p
−
1
x
−
1
1
−
G
(
x
)
=
x
−
1
x
p
−
1
G
(
x
)
=
x
p
−
x
x
p
−
1
G
(
x
)
=
1
+
1
x
p
−
1
−
x
⋅
1
x
p
−
1
G
(
x
)
=
∑
i
=
0
+
∞
x
p
i
+
1
−
∑
i
=
1
+
∞
x
p
i
\begin{aligned} \cfrac{-1}{G(x)-1}&=\cfrac{x^p-1}{x-1}\\[2ex] 1-G(x)&=\cfrac{x-1}{x^p-1}\\[2ex] G(x)&=\cfrac{x^p-x}{x^p-1}\\[2ex] G(x)&=1+\cfrac{1}{x^p-1}-x\cdot \cfrac{1}{x^p-1}\\[2ex] G(x)&=\sum_{i=0}^{+\infty} x^{pi+1}-\sum_{i=1}^{+\infty} x^{pi} \end{aligned}
G(x)−1−11−G(x)G(x)G(x)G(x)=x−1xp−1=xp−1x−1=xp−1xp−x=1+xp−11−x⋅xp−11=i=0∑+∞xpi+1−i=1∑+∞xpi
因此,
g
i
=
{
1
,
i
≡
1
(
m
o
d
n
−
k
+
1
)
−
1
,
i
≡
0
(
m
o
d
n
−
k
+
1
)
0
,
i
≢
1
或
0
g_i=\begin{cases} 1,& i\equiv 1\pmod{n-k+1}\\[2ex] -1,& i\equiv 0\pmod{n-k+1}\\[2ex] 0,& i\not\equiv1\text{或}0 \end{cases}
gi=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧1,−1,0,i≡1(modn−k+1)i≡0(modn−k+1)i≡1或0
然后DP就好了。因为有值的
g
g
g只有
n
k
\cfrac{n}{k}
kn个,所以直接暴力转移就可以了。时间复杂度
O
(
n
2
log
2
n
)
O(n^2\log_2 n)
O(n2log2n)。
CODE
#include<cstdio>
#include<cstring>
using namespace std;
#define N 1005
int P,inv[N],fac[N],f[N],ans[N];
inline int C(int x,int y){return 1LL*fac[x]*inv[y]%P*inv[x-y]%P;}
inline void add(int &x,int y){x+=y;if(x>=P) x-=P;}
inline void minus(int &x,int y){x-=y;if(x<0) x+=P;}
int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
int n,k,p;
scanf("%d%d",&n,&P);
fac[0]=1;for(int i=1;i<N;++i) fac[i]=1LL*fac[i-1]*i%P;
inv[1]=1;for(int i=2;i<N;++i) inv[i]=1LL*inv[P%i]*(P-P/i)%P;
inv[0]=1;for(int i=1;i<N;++i) inv[i]=1LL*inv[i-1]*inv[i]%P;
ans[0]=fac[n];
for(int k=1;k<n;++k)//这里枚举的是上文中的n-k,p=n-k+1.
{
p=k+1,memset(f,0,sizeof f),f[0]=1;
for(int i=0;i<n;++i) if(f[i])
{
for(int j=1;i+j<=n;j+=p) add(f[i+j],1LL*f[i]*C(n-i,j)%P);
for(int j=p;i+j<=n;j+=p) minus(f[i+j],1LL*f[i]*C(n-i,j)%P);
}
ans[k]=fac[n]-f[n];
if(ans[k]<0) ans[k]+=P;
}
for(int i=0;i<n;++i) printf("%d\n",ans[n-i-1]);
return 0;
}