1537E - Erase and Extend
题目描述
你可以对一个长度为 n n n 的字符串 s s s 进行任意次如下操作
- 去掉 s s s 末尾的字符。
- 把 s s s 赋值为 s + s s+s s+s 。
求一个操作后的、长度恰好为 k k k 的、字典序最小的字符串。
数据范围与提示
1 ≤ n , k ≤ 5 ⋅ 1 0 5 1\le n,k\le 5\cdot 10^5 1≤n,k≤5⋅105 。
前言
好久没碰到字符串的题了,做起来真舒服。
思路
注意到操作后的字符串一定是若干个 s s s 的前缀拼起来然后重复,再掐尾的结果。
首先有一个 E a s y V e r s i o n \rm Easy\; Version EasyVersion 的结论:最终的字符串一定是 s s s 的某一个前缀经过重复然后掐尾得来的。
考虑用反证法证明:如果是两个长度不相等的前缀拼一起然后重复(假设是 A B AB AB 和 A A A ),那么为什么不选 ( A B ) ( A B ) . . . (AB)(AB)... (AB)(AB)... 而选 ( A B A ) ( A B A ) . . . (ABA)(ABA)... (ABA)(ABA)... 呢?一定是串 A B A A ABAA ABAA 比串 A B A B ABAB ABAB 的字典序更小。这说明 A A A 比 B B B 的字典序小,那么显然有 ( A ) ( A ) . . . (A)(A)... (A)(A)... 比 ( A B A ) ( A B A ) . . . (ABA)(ABA)... (ABA)(ABA)... 更优,这就矛盾了。
同理,其它情况也可以用类似方法否定掉,最后只可能是一个前缀的重复最优。
到这里已经可以用暴力过 E a s y V e r s i o n Easy\;Version EasyVersion 了,而 H a r d V e r s i o n Hard\;Version HardVersion 有很多做法,如二分Hash比较、SA等。这里提供一个强结论支持的简单做法。
(为了方便,我们把前缀 0 ∼ i 0\sim i 0∼i 经重复后的串叫做前缀构造串 i i i )枚举每个前缀,假设 i i i 以前字典序最小的前缀构造串为 a a a ,那么把 s i s_i si 和 s i % a s_{i\%a} si%a 进行比较:
- 若
s
i
>
s
i
%
a
s_i>s_{i\%a}
si>si%a ,直接
break
; - 若 s i = s i % a s_i=s_{i\%a} si=si%a ,不管它;
- 若 s i < s i % a s_i<s_{i\%a} si<si%a ,把 a a a 赋值为 i i i 。
这样最后得到的 a a a 即为最优的前缀。
证明一下为什么是正确的:
-
若 s i > s i % a s_i>s_{i\%a} si>si%a ,直接退出循环。此时表明前缀 i i i 的字典序已经大于构造串 a a a ,那么 i i i 后面的前缀的字典序一定也大于 a a a ,所以后面都不用找了。
-
若 s i = s i % a s_i=s_{i\%a} si=si%a ,不处理。由于有第一条存在,所以前缀构造串 a a a 中的字符一定都 ≤ s 0 \le s_0 ≤s0 ,此时分情况讨论:
- 若 s i < s 0 s_i<s_0 si<s0 ,那么如果构造串 a a a 的下一个字符 < s 0 <s_0 <s0 ,则 i i i 肯定不优;若 = s 0 =s_0 =s0 ,则 i i i 也不优(否则一定有一个 i i i 以前的前缀优于 a a a ,矛盾)。
- 若 s i = s 0 s_i=s_0 si=s0 ,那么显然 i − 1 i-1 i−1 比 i i i 更优。
-
若 s i < s i % a s_i<s_{i\%a} si<si%a ,把 a a a 赋值为 i i i 。由于前面见大于就退出循环的操作,所以构造串 i i i 与 a a a 的前 i − 1 i-1 i−1 个字符一定是相同的,显然此时 i i i 就比 a a a 更优,需要更新答案。
这里的讨论最多只嵌套了三层,其实省略了一些,原本需要讨论得更深。迫于篇幅,用“显然”“一定”替之。
所以最终复杂度 O ( n ) O(n) O(n) ,最多跑 31 m s 31ms 31ms 。
代码
#include<cstdio>//JZM YYDS!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<ctime>
#define ll long long
#define MAXN 500005
#define uns unsigned
#define MOD 998244353ll
#define INF 1e15
#define lowbit(x) ((x)&(-(x)))
using namespace std;
inline ll read(){
ll x=0;bool f=1;char s=getchar();
while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
return f?x:-x;
}
int n,k;
char s[MAXN];
signed main()
{
n=read(),k=read();
scanf("%s",s+1);
int a=1;
for(int i=2;i<=min(n,k);i++){
int p=(i-1)%a+1;
if(s[p]>s[i])a=i;
else if(s[p]<s[i])break;
}
for(int i=1;i<=k;i++)putchar(s[(i-1)%a+1]);
putchar('\n');
return 0;
}