CF1537E - Erase and Extend——结论题

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 1n,k5105

前言

好久没碰到字符串的题了,做起来真舒服。

思路

注意到操作后的字符串一定是若干个 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 0i 经重复后的串叫做前缀构造串 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 i1 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 i1 个字符一定是相同的,显然此时 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值