[CF1526E]Oolimry and Suffix Array

C++ 同时被 3 个专栏收录
442 篇文章 0 订阅
224 篇文章 1 订阅

题目

传送门 to CF

题意概要
当字符集大小为 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[sain]<s[sajn],这里是字典序比较。

数据范围与提示
n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×105 。狡猾的出题人告诉你 k ≤ 2 × 1 0 5 k\le 2\times 10^5 k2×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 be2×105 so as to hide the final complexity 😃

前言

难道 n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×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 sisj 。同时,如果 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 sjsi 时就已经满足 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=1x1xk+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](1x1xk+1)p(1xxxk+1)q=[xk](1x)p+qxq=[xkq](1x)p+q1=(p+q1p+k1)

因为 [ 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(iqp)(ik),确实更容易想一些。常数肯定不优秀

借用上面的思路,得到 ( p + k − 1 p + q − 1 ) {p+k-1\choose p+q-1} (p+q1p+k1) 可以是隔板法。考虑差分数组,相当于一些位置为正,另一些位置为自然数。假设 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+1sn=k,把 p p p 个自然数全部加成正数,得到的就是 ( k + p − 1 n ) {k+p-1\choose n} (nk+p1) 。我的式子中 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} k10100000 也可以,因为真的与它完全无关(反正有取模,不用高精度)。

代码

#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;
}
  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值