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