题目
题意概要
当字符集大小为
k
k
k 时,给出一个后缀数组
s
a
sa
sa,问有多少个长度为
n
n
n 的字符串能够得到这一结果。
R e m i n d e r \rm Reminder Reminder:后缀数组 s a sa sa 满足,将一个字符串的所有后缀 l e x i c o g r a p h i c a l l y \rm lexicographically lexicographically 排序,排在第 i i i 位的是 s a i sa_i sai 开头的后缀。或者直白一点:对于任意 i < j i<j i<j 都成立 s [ s a i ∼ n ] < s [ s a j ∼ n ] s[sa_i\sim n]<s[sa_j\sim n] s[sai∼n]<s[saj∼n],这里是字典序比较。
数据范围与提示
n
≤
2
×
1
0
5
n\le 2\times 10^5
n≤2×105 。狡猾的出题人告诉你
k
≤
2
×
1
0
5
k\le 2\times 10^5
k≤2×105,理由是
Btw k was chosen to also be ≤ 2 × 1 0 5 so as to hide the final complexity \text{Btw k was chosen to also be}≤2×10^5\text{ so as to hide the final complexity} Btw k was chosen to also be≤2×105 so as to hide the final complexity 😃
前言
难道
n
≤
2
×
1
0
5
n\le 2\times 10^5
n≤2×105 但是
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 过不了样例(会 TLE
),这是合乎道义的事吗?
你的机子烂,烂的像 💩,猪 D Y M \sf DYM DYM 不如,烂的像马桶,你就直说:请给我打 O ( n ) \mathcal O(n) O(n),否则我一律卡掉。为什么有的家伙,又烂又不坦诚?
虽然我也有点蠢,确实没想着多写一步,多写一步就够了……
思路
先把 s a sa sa 转化为 r n k rnk rnk 吧,方便一点。
如果 r n k ( i ) < r n k ( j ) rnk(i)<rnk(j) rnk(i)<rnk(j),那么 s i ≤ s j s_i\le s_j si≤sj 。同时,如果 r n k ( i + 1 ) > r n k ( j + 1 ) rnk(i+1)>rnk(j+1) rnk(i+1)>rnk(j+1),则 s i < s j s_i<s_j si<sj 。
显然上面的限制是充要的。
那么,按照 r n k rnk rnk 排序,则 s i s_i si 单调不降。找左边的 r n k ( i + 1 ) > r n k ( j + 1 ) rnk(i+1)>rnk(j+1) rnk(i+1)>rnk(j+1),其实只需要找最右边的一个(毕竟 s i s_i si 单调不降)。
如果你手玩一下,你会发现,如果将 > > > 或者 ≥ \ge ≥ 关系连边,那么 得到的是一条链。即,每个字符 只需要 大于等于上一个,或者只需要严格大于它。只需要判断前一个点的 r n k ( i + 1 ) rnk(i+1) rnk(i+1) 是否大于 r n k ( j + 1 ) rnk(j+1) rnk(j+1) 即可。
可以用归纳法证明。如果需要严格大于上一个字符,显然没有比这个更强的限制;如果不需要严格大于,即上一个 r n k ( i + 1 ) rnk(i+1) rnk(i+1) 比较小,那么实际找到的 r n k ( x + 1 ) rnk(x+1) rnk(x+1) 当然也有 r n k ( x + 1 ) > r n k ( i + 1 ) rnk(x+1)>rnk(i+1) rnk(x+1)>rnk(i+1),进而 s x < s i s_x<s_i sx<si 。那么 s j ≥ s i s_j\ge s_i sj≥si 时就已经满足 s j > s x s_j>s_x sj>sx 了,所以只需要大于等于上一个字符。
如果用
d
p
\tt dp
dp 的思维,
f
(
i
)
f(i)
f(i) 表示以第
i
i
i 大的字符结尾,那么两种转移都类似于前缀和。那么我们借用一下多项式,设
F
(
x
)
=
∑
i
=
0
k
x
i
=
1
−
x
k
+
1
1
−
x
F(x)=\sum_{i=0}^{k}x^i=\frac{1-x^{k+1}}{1-x}
F(x)=∑i=0kxi=1−x1−xk+1,那么答案就是
[
x
k
]
F
(
x
)
p
⋅
[
F
(
x
)
−
1
]
q
[x^k]\;F(x)^{p}\cdot[F(x)-1]^{q}
[xk]F(x)p⋅[F(x)−1]q
因为 F ( x ) F(x) F(x) 就是求完整前缀和,而 F ( x ) − 1 F(x)-1 F(x)−1 就是严格小于的前缀和。初状态可以看成 F ( x ) − 1 F(x)-1 F(x)−1,最终求和可以看成 F ( x ) F(x) F(x) 。所以 p p p 就是 ≥ \ge ≥ 的个数 + 1 +1 +1,而 q q q 是 > > > 的个数 + 1 +1 +1 。
显然我们有 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的多项式做法。但是无法通过!令人心寒!
我们再化简一下。
[
x
k
]
F
(
x
)
p
⋅
[
F
(
x
)
−
1
]
q
=
[
x
k
]
(
1
−
x
k
+
1
1
−
x
)
p
(
x
−
x
k
+
1
1
−
x
)
q
=
[
x
k
]
x
q
(
1
−
x
)
p
+
q
=
[
x
k
−
q
]
1
(
1
−
x
)
p
+
q
=
(
p
+
k
−
1
p
+
q
−
1
)
[x^k]\;F(x)^p\cdot [F(x)-1]^{q}\\ =[x^k]\;\left(\frac{1-x^{k+1}}{1-x}\right)^p\left(\frac{x-x^{k+1}}{1-x}\right)^q\\ =[x^k]\;\frac{x^q}{(1-x)^{p+q}}\\ =[x^{k-q}]\;{1\over (1-x)^{p+q}}\\ ={p+k-1\choose p+q-1}
[xk]F(x)p⋅[F(x)−1]q=[xk](1−x1−xk+1)p(1−xx−xk+1)q=[xk](1−x)p+qxq=[xk−q](1−x)p+q1=(p+q−1p+k−1)
因为 [ x k ] [x^k] [xk] 的缘故, x k + 1 x^{k+1} xk+1 被直接丢弃了。然后就得到了这么简单的式子!
D
D
G
\sf DDG
DDG 还有一种做法,直接枚举有多少种字母,然后在那些
≥
\ge
≥ 的位置中找到一些分界点。形如
∑
i
=
q
p
+
q
(
p
i
−
q
)
(
k
i
)
\sum_{i=q}^{p+q}{p\choose i-q}{k\choose i}
∑i=qp+q(i−qp)(ik),确实更容易想一些。常数肯定不优秀。
借用上面的思路,得到 ( p + k − 1 p + q − 1 ) {p+k-1\choose p+q-1} (p+q−1p+k−1) 可以是隔板法。考虑差分数组,相当于一些位置为正,另一些位置为自然数。假设 s n + 1 = k s_{n+1}=k sn+1=k,那么这个长度为 n + 1 n+1 n+1 的差分数组的和就是 s n + 1 − s n = k s_{n+1}-s_{n}=k sn+1−sn=k,把 p p p 个自然数全部加成正数,得到的就是 ( k + p − 1 n ) {k+p-1\choose n} (nk+p−1) 。我的式子中 p + q = n + 1 p+q=n+1 p+q=n+1,所以二者是完全一样的。
看上去我们要把 r n k ( i ) rnk(i) rnk(i) 拿来排序,实际上早就排好序了——就是题目中给出的 s a sa sa 数组嘛!
时间复杂度 O ( n ) \mathcal O(n) O(n),哪怕 k ≤ 1 0 100000 k\le 10^{100000} k≤10100000 也可以,因为真的与它完全无关(反正有取模,不用高精度)。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int Mod = 998244353;
const int MaxN = 400005;
int sa[MaxN], rnk[MaxN];
int_ inv[MaxN]; // int_ is good
int main(){
int n = readint(), k = readint();
for(int i=1; i<=n; ++i){
sa[i] = readint()+1;
rnk[sa[i]] = i;
}
rnk[n+1] = 0; // smallest
int cnt0 = 0, cnt1 = 0, ans;
for(int i=2; i<=n; ++i)
if(rnk[sa[i-1]+1] > rnk[sa[i]+1])
++ cnt1; // strictly greater
else ++ cnt0; // greater or equal
++ cnt1, ++ cnt0; // origin & sum
int N = k+cnt0-1, M = cnt0+cnt1-1;
rep(i,(ans=1)+(inv[1]=1),M){
inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;
ans = ans*inv[i]%Mod; // div M!
}
rep(i,1,M) ans = ans*(N+1ll-i)%Mod;
if(N < M) ans = 0; // bad combination
printf("%d\n",ans);
return 0;
}