SNOI2019 字符串 题解

$\text{Description}$

给定一个字符串 $s[1\cdots n]$,定义字符串 $s_i$ 为 $s[1\cdots i-1]+s[i+1\cdots n]$,试为 $s_1,s_2,\cdots,s_n$ 排序。

$n\le 10^6$

$\text{Solution}$

一个朴素的想法是预处理所有串然后暴力排序,时间复杂度 $O(n^2\log n)$。

回顾字符串的排序:从前往后一位一位比较,若相同则继续比较,否则可以结束。

那么 $s_1,s_2,\cdots,s_n$ 的最大特点就是:重复字符多。

我们从具体情况入手(以下字符虽然表示上相同,但实际情况中可能不相同):

首先我们从第一位开始比较,容易发现 $s_2,s_3,s_4,s_5$ 的第一位都为 $\texttt{!}$,故我们只需比较 $\texttt{!}$ 与 $\texttt{?}$,接下来分三类讨论。

$\texttt{!}=\texttt{?}$。此时可以消去所有串的第一位,我们将得到:

容易发现 $s_1$ 与 $s_2$ 相同,由于题目要求编号小的在前面,所以我们得到了 $s_1$ 的排名恰好在 $s_2$ 前。然后我们将 $s_1$ 略去,现在比较剩下的串时,就归约到规模更小而形式相同的情形了。

$\texttt{!}<\texttt{?}$。此时 $s_1$ 是当前的串中排名最末的串,随后我们将 $s_1$ 及 $\texttt{!}$ 略去,即可归约到规模更小而形式相同的情形了。

$\texttt{?}<\texttt{!}$。此时 $s_1$ 是当前的串中排名最前的串,随后就与上面的那个情形同理了。

于是可以逐步完成整个过程,其中第一种情况可以把前者和后者绑定在一起,当后者可以被塞入当前最前或最末时按照一定顺序一并塞入即可。

最后一个串只需让 $s[n]$ 和空气比较就行了,具体细节见代码。

$\text{Code}$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<bits/stdc++.h>
#define REG register
using namespace std;

int n;

char str[1000005];

int Pre[1000005],Stk[1000005],top;

int Ans[1000005],Head,Tail;

inline void Work(){
scanf("%d",&n);
scanf("%s",str+1);
for(REG int i=1;i<=n;++i) Pre[i]=i;
Tail=n+1;
for(REG int i=1;i<=n;++i){
if(str[i]==str[i+1]) Pre[i+1]=i;
else
if(str[i]<str[i+1]){
int Now=i;
while(Pre[Now]^Now) Ans[--Tail]=Now,Now=Pre[Now];
Ans[--Tail]=Now,Now=Pre[Now];
}
else{
int Now=i;top=0;
while(Pre[Now]^Now) Stk[++top]=Now,Now=Pre[Now];
Stk[++top]=Now,Now=Pre[Now];
while(top) Ans[++Head]=Stk[top--];
}
}
for(REG int i=1;i<=n;++i) printf("%d ",Ans[i]);
}

int main(){Work();}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×