题面

给一个长度为

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;
}

最新文章

  1. [LeetCode] Jump Game 跳跃游戏
  2. Java读取Level-1行情dbf文件极致优化(2)
  3. 修改Shp文件名称
  4. sum() 函数
  5. 百度网盘,前几天刚从百度云改名过来,百度云这个名字给之前的百度开放云(同步盘用户比较小众)good
  6. 搭建Titanium开发环境
  7. HTML textarea输入框限制长度 (引)
  8. Set up JBPM5.4 Final Installer to use MS SQL Server 2008 using JTDS(转)
  9. RTP 记录 log 该机制
  10. JQuery hover toggle事件使用
  11. 《Javascript_Dom 编程艺术》(第2版)读书笔记
  12. redux 最简例子
  13. 【算法导论】B树
  14. ES 6 系列 - 变量声明
  15. VS2013打包程序步骤
  16. linux top命令看到的实存(RES)与虚存(VIRT)分析
  17. 【Spark】SparkStreaming-如何使用checkpoint
  18. windows自带杀毒防火墙
  19. 设计社区类Web原型制作分享-Behance
  20. js动态创建Form表单并提交

热门文章

  1. LVGL库入门教程01-移植到STM32(触摸屏)
  2. 在Winform开发中,使用Async-Awati异步任务处理代替BackgroundWorker
  3. C#中常用的目录|文件|路径信息操作
  4. Redis的内存淘汰策略(八)
  5. 面试官:Redis 过期删除策略和内存淘汰策略有什么区别?
  6. 广义径向基网络(RBF网络)
  7. 一个月后,我们又从 MySQL 双主切换成了主 - 从!
  8. 从入门到爱上Git
  9. vue3代码编写
  10. intellidea 快捷键-*01