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