[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 1n,k5000.
  • Hard Version 1 ≤ n , k ≤ 500   000 \tt1\leq n,k\leq500\,000 1n,k500000.

Sample(Unofficial)

Input

8 10
dbcabdca

Output

dbcabdbcab

题解

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

  • 证明:首先,不在乎是否相同,最终的串至少是许多个前缀组成的,这点毋庸置疑。然后,如果中途出现了两个不同的前缀挨在一起: … [ 1 … i ] [ 1 … j ] … \tt\ldots[1\ldots i][1\ldots j]\ldots [1i][1j] ,由于不同,字典序大小一定有差异,若 [ 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 ba b \tt b b 的一个字符串border 。此时若 a ≤ b 2 \tt a\leq\frac{b}{2} a2b ,则 a \tt a a b \tt b b 的循环节,两者等价;否则,再查询一次 [ 1... b ] \tt[1...b] [1...b] [ 1... b − a ] \tt[1...b-a] [1...ba] 就是了。

在比较某个后缀和整个串的字典序时,可以用扩展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(i1)%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 i1 要么等于 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(i1)%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(i1)%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;
}
posted @ 2021-06-21 22:46  DD_XYX  阅读(27)  评论(0编辑  收藏  举报