题面

给一个长度为

n

\tt n

n 的字符串,你可以进行无限次以下两种操作之一:

  • 删去末尾的字符(此时要保证删去后字符串非空)。
  • 把当前整个字符串复制一份,接到自己的后面。

输出最终通过操作能达到的长度为

k

\tt k

k 的字符串字典序最小的那个字符串。

  • Easy Version

    1

    n

    ,

    k

    5

    000

    \tt1\leq n,k\leq5\,000

    1≤n,k≤5000.

  • Hard Version

    1

    n

    ,

    k

    500

    000

    \tt1\leq n,k\leq500\,000

    1≤n,k≤500000.

Sample(Unofficial)

Input

8 10
dbcabdca

Output

dbcabdbcab

题解

有这么一个结论:最终的串一定是某个前缀重复多次组成的。

  • 证明:首先,不在乎是否相同,最终的串至少是许多个前缀组成的,这点毋庸置疑。然后,如果中途出现了两个不同的前缀挨在一起:

    [

    1

    i

    ]

    [

    1

    j

    ]

    \tt\ldots[1\ldots i][1\ldots j]\ldots

    …[1…i][1…j]… ,由于不同,字典序大小一定有差异,若

    [

    1...

    j

    ]

    <

    [

    1...

    i

    ]

    \tt[1...j]<[1...i]

    [1...j]<[1...i] ,则不如把俩前缀互换,如果

    [

    1...

    i

    ]

    <

    [

    1...

    j

    ]

    \tt[1...i]<[1...j]

    [1...i]<[1...j] ,不如变成

    [

    1...

    i

    ]

    [

    1...

    i

    ]

    ×

    k

    .

    .

    .

    \tt[1...i][1...i]\times k...

    [1...i][1...i]×k..., 再在后面做些改动。存在不同前缀组成的串一定不是最优的,因此,最优的串一定是某个前缀重复多次组成的。

Easy Version

既然确定了是某个前缀组成的,那么就只有最多

n

\tt n

n 种情况,每次

Θ

(

k

)

\tt\Theta(k)

Θ(k) 比较两串大小,足以通过。

CODE

#include<map>
#include<queue>
#include<ctime>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 5005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define eps 1e-9
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
char ss[MAXN];
bool cmp(int a,int b) {
int ad1 = 1,ad2 = 1;
for(int i = 1;i <= k;i ++) {
if(ss[ad1] != ss[ad2]) return ss[ad1] < ss[ad2];
ad1 ++; ad2 ++;
if(ad1 > a) ad1 -= a;
if(ad2 > b) ad2 -= b;
}
return 0;
}
int main() {
n = read();k = read();
scanf("%s",ss + 1);
int as = 1;
for(int i = 1;i <= n;i ++) {
if(cmp(i,as)) as = i;
}
int ad = 1;
for(int i = 1;i <= k;i ++) {
putchar(ss[ad]);
ad ++; if(ad > as) ad -= as;
}ENDL;
return 0;
}

Hard Version

My Solution

比较两个前缀

[

1...

a

]

\tt[1...a]

[1...a] 和

[

1...

b

]

\tt[1...b]

[1...b] 时,不妨设

a

<

b

\tt a<b

a<b ,那么可以先比较两后缀

[

1...

]

\tt[1...]

[1...] 和

[

a

+

1...

]

\tt[a+1...]

[a+1...] ,如果在小于等于

b

\tt b

b 的范围内无差别的话,说明

b

a

\tt b-a

b−a 是

b

\tt b

b 的一个字符串border 。此时若

a

b

2

\tt a\leq\frac{b}{2}

a≤2b​ ,则

a

\tt a

a 是

b

\tt b

b 的循环节,两者等价;否则,再查询一次

[

1...

b

]

\tt[1...b]

[1...b] 和

[

1...

b

a

]

\tt[1...b-a]

[1...b−a] 就是了。

在比较某个后缀和整个串的字典序时,可以用扩展KMP求该后缀和整个串的最长公共前缀。当然,也可以因为忘了扩展KMP怎么写所以奢侈地用后缀数组+处理

h

i

g

h

t

\tt hight

hight 数组代替解决。

CODE

后者

#include<map>
#include<queue>
#include<ctime>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 500005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define eps 1e-9
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
char ss[MAXN];
int sa[MAXN],rk[MAXN];
int hd[MAXN],tl[MAXN],nx[MAXN],pr[MAXN<<1];
int ins(int i,int x) {return tl[i] ? (nx[tl[i]] = x):(hd[i] = x);}
void Suffix_Array(char *s,int *sa,int *rk,int n) {
for(int i=1;i<=n;i++) sa[i]=rk[i]=pr[n+i]=hd[i]=tl[i]=nx[i]=0;
for(int i = 1;i <= n;i ++) {
nx[tl[s[i]] = ins(s[i],i)] = 0;
}
int cnt = 0,nm = 0;
for(int i = 0;i <= 256;i ++) {
int p = hd[i]; if(p) nm ++;
while(p) {
sa[++ cnt] = p; rk[p] = nm;
if(p == tl[i]) break;
p = nx[p];
} tl[i] = hd[i] = 0;
}
for(int ii = 1;ii <= n;ii <<= 1) {
for(int i = 1;i <= n;i ++) pr[i] = rk[i],rk[i] = 0;
for(int i = n-ii+1;i <= n;i ++) {
nx[tl[pr[i]] = ins(pr[i],i)] = 0;
}
for(int i = 1;i <= n;i ++) {
if(sa[i]-ii < 1) continue;
nx[tl[pr[sa[i]-ii]] = ins(pr[sa[i]-ii],sa[i]-ii)] = 0;
}
int cnt = 0,nm = 0;
for(int i = 1;i <= n;i ++) {
int p = hd[i],pp = 0;
while(p) {
sa[++ cnt] = p;
rk[p] = (!pp || pr[p+ii]!=pr[pp+ii] ? ++nm:nm);
if(p == tl[i]) break;
pp = p;p = nx[p];
} tl[i] = hd[i] = 0;
}
}
return ;
}
int hi[MAXN],h[MAXN];
void INIT_H(char *s,int *sa,int *rk,int *hi,int n) {
hi[0] = 0;s[n+1] = 0;
for(int i = 1;i <= n;i ++) {
int kk = sa[rk[i]-1]; if(!kk){hi[i]=0;continue;}
hi[i] = max(0,hi[i-1]-1);
while(s[i+hi[i]] == s[kk+hi[i]]) hi[i] ++;
}return ;
}
int f[MAXN];
bool cmp(int a,int b,int n) {
a = min(a,n),b = min(b,n);
if(a > b) return !cmp(b,a,n);
if(a == b) return 0;
int st = a+1,le = b-a;
if(f[rk[st]] >= le) {
if(a * 2 <= b) return 0;
return !cmp(le,b,n-a);
}
return rk[st] > rk[1];
}
int main() {
n = read();k = read();
scanf("%s",ss + 1);
for(int i = n+1;i <= k;i ++) {
ss[i] = ss[i-n];
}
Suffix_Array(ss,sa,rk,k);
INIT_H(ss,sa,rk,hi,k);
int ad = rk[1];
for(int i = 1;i <= k;i ++) h[i] = hi[sa[i]];
f[ad] = k;
for(int i = ad-1;i > 0;i --) {
f[i] = min(f[i+1],h[i+1]);
}
for(int i = ad+1;i <= k;i ++) {
f[i] = min(f[i-1],h[i]);
}
int as = 1;
for(int i = 1;i <= k;i ++) {
if(cmp(i,as,k)) as = i;
}
ad = 1;
for(int i = 1;i <= k;i ++) {
putchar(ss[ad]);
ad ++; if(ad > as) ad -= as;
}ENDL;
return 0;
}

God’s Solution

神奇的题解做法:

i

\tt i

i 从

1

\tt1

1 到

n

\tt n

n 枚举,更新

c

h

o

o

s

e

\tt choose

choose,每次比较

S

i

\tt S_{i}

Si​ 是否小于

S

(

i

1

)

%

c

h

o

o

s

e

+

1

\tt S_{(i-1)\%choose+1}

S(i−1)%choose+1​ ,如果是,那么

c

h

o

o

s

e

:

=

i

\tt choose := i

choose:=i,如果大于,跳出循环。最终的

c

h

o

o

s

e

\tt choose

choose 即为我们要的那个前缀。

!?

其实很好证。由于

c

h

o

o

s

e

\tt choose

choose 是前面处理出的最优前缀,因此,

i

1

\tt i-1

i−1 要么等于

c

h

o

o

s

e

\tt choose

choose ,要么不优,此时决定成败的只能是

S

i

\tt S_i

Si​ 了。如果

S

i

>

S

(

i

1

)

%

c

h

o

o

s

e

+

1

\tt S_i>S_{(i-1)\%choose+1}

Si​>S(i−1)%choose+1​ 那么自然没戏,并且由于它是后面所有前缀的前缀,后面的位置也没戏了,可以直接

b

r

e

a

k

\tt break

break 了。如果

S

i

<

S

(

i

1

)

%

c

h

o

o

s

e

+

1

\tt S_i<S_{(i-1)\%choose+1}

Si​<S(i−1)%choose+1​ ,由于前面没有

b

r

e

a

k

\tt break

break ,因此前面都相等,这一位更小,肯定就更优了啊!

CODE

Impressed?

//By C20200522
#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;
}

[CF1537E] Erase and Extend (字符串)的更多相关文章

  1. C语言字符串操作总结大全

    1)字符串操作 strcpy(p, p1)  复制字符串  函数原型strncpy(p, p1, n)   复制指定长度字符串  函数原型strcat(p, p1)   附加字符串  函数原型strn ...

  2. C语言字符串操作总结大全(超详细)

    本篇文章是对C语言字符串操作进行了详细的总结分析,需要的朋友参考下 1)字符串操作  strcpy(p, p1) 复制字符串  strncpy(p, p1, n) 复制指定长度字符串  strcat( ...

  3. c语言的字符串操作(比较详细)

    1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...

  4. c++学习-字符串

    字符数组和 string类型比较的区别 #include<iostream> #include<string> using namespace std; class area{ ...

  5. C语言字符串操作函数集

    1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...

  6. C语言字符串操作详细总结

    1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...

  7. 面试之C语言字符串操作总结大全(转载)

    趁着十一就好好补补数据结构吧,通信这个不软不硬的专业,现在还是得好好学学补习补习,,你这个非211的本科生!虽然拿到了一个offer,但是觉得时间还有,得继续拼一拼,希望不辜负! 1)字符串操作 st ...

  8. C语言学习笔记 (008) - C语言字符串操作总结大全(超详细)(转)

    1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...

  9. C 和 C++ 字符串函数操作

    1)字符串操作  strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长 ...

随机推荐

  1. 如何优化PlantUML流程图(时序图)

    这篇文章用来介绍,如何画出好看的流程图. 1. 选择合适的组件 1.1 plantuml官方提供的组件 1.2 加载图片 1.2.1 加载本地图片 1.2.2 加载网络图片 1.2.3 图片资源 2. ...

  2. python新建一个目录

    源码部分 import os # 创建目录 def mkdir(path): isExists = os.path.exists(path) if not isExists: os.makedirs( ...

  3. ansible安装配置及基本用法

    ansiblle具有如下特点: 1.部署简单,只需在主控端部署Ansible环境,被控端无需做任何操作: 2.默认使用SSH协议对设备进行管理: 3.主从集中化管理: 4.配置简单.功能强大.扩展性强 ...

  4. RPA应用场景-银行回单查询

    场景概述银行回单查询 所涉系统名称银行网银 人工操作(时间/次) 5 分钟 所涉人工数量 4 操作频率不定时 场景流程 1.收到外派业务员申请查询收入银行回单的邮件: 2.依据邮件中提供的客户信息进入 ...

  5. BetterScroll源码阅读顺便学习TypeScript

    开头 TypeScript已经出来很多年了,现在用的人也越来越多,毋庸置疑,它会越来越流行,但是我还没有用过,因为首先是项目上不用,其次是我对强类型并不敏感,所以纯粹的光看文档看不了几分钟就心不在焉, ...

  6. 所有人都说Python 简单易学,为何我觉得难?

    来谈谈心 记得刚学Python的时候,几乎所有人都说Python 简单易学,而对于编程零基础,只掌握Word和Excel的人来说,感觉真的好难. 学习之前网上的教材看了,Python的书也看了,包括& ...

  7. 使用net core 6 c# 的 NPOI 包,读取excel..xlsx单元格内的图片,并存储到指定服务器

    这个是记录,单元格的图片. 直接上代码,直接新建一个 net core api 解决方案,引用一下nuget包.本地创建一个 .xlsx 格式的excel文件 using ICSharpCode.Sh ...

  8. 现代化CSS

    Less Sass less Sass 与Less相比SASS更适合大型,底层的开发 Compass CSS核心技巧 CSS应用 现代化CSS方法论 CSS分层与面向对象 为什么要对CSS分层 CSS ...

  9. 【cartographer_ros】六: 发布和订阅路标landmark信息

    上一节介绍了陀螺仪Imu传感数据的订阅和发布. 本节会介绍路标Landmark数据的发布和订阅.Landmark在cartographer中作为定位的修正补充,避免定位丢失. 这里着重解释一下Land ...

  10. Java 技术栈中间件优雅停机方案设计与实现全景图

    欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...