题面

给一个长度为

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. Python装饰器Decorators

    def hi(name="yasoob"): return "hi " + name print(hi()) # 我们甚至可以将一个函数赋值给一个变量,比如 g ...

  2. .NET打包应用设置成自包含

    设置项目的配置文件 在项目的配置文件(.csproj文件)中加入RuntimeIdentifier节点,节点的内容为要打包进入最终程序的目标运行时.更多平台标识符,请看这里RIDs. <Prop ...

  3. Python if-else的简单表示

    常见写法 a = 1 b = 1 c = 2 if a == b: print("true") elif a == c: print("false") else ...

  4. python 基础知识-day10(面向对象)

    1.面向对象的概念 拥有共同属性的一类进行归类的过程叫做面向对象. 2.注意事项 class定义类的时候,类名的首字母必须大写 3.面向对象案例 1 class Person(object): 2 d ...

  5. 使用c++爬取股市数据,获取最新行情

    最近自己动手写个小软件(界面原生态,还没来得及加样式哈).每天看看潜力股懒人做法,不介意推荐.资源有限,只能观察一下低价股,分析一下运动规律,什么时候拉升,惯性如何 主要功能:读取网络数据:保存本地数 ...

  6. HTML:<input type="text"> 输入数字时的验证!(在提交时验证)

    <!--非负数:<input type="text" name="" pattern="^\d+$">--> < ...

  7. 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾

    「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 目录 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 Part 0 第四单元作业架构设计 架构设计概要 AppRun ...

  8. .Net之延迟队列

    介绍 具有队列的特性,再给它附加一个延迟消费队列消息的功能,也就是说可以指定队列中的消息在哪个时间点被消费. 使用场景 延时队列在项目中的应用还是比较多的,尤其像电商类平台: 订单成功后,在30分钟内 ...

  9. JDK9对集合添加的优化of方法和Debug追踪

    JDK9对集合添加的优化(of方法) JDK9的新特性: 1.List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素 2.static List of (E- ...

  10. springboot2+jpa+oracle实例

     pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...