[SNOI2019]字符串题解

文章目录

  • 题目链接
  • sol
    • 10%数据
    • 另20%数据
    • 另30%数据
    • 最后40%数据
  • code:

题目链接

洛谷P5329

sol

题目意思很明确,但似乎不太好求。还是先看看部分分。

10%数据

Θ ( n 2 log ⁡ n ) \Theta(n^2\log n) Θ(n2logn),暴力,先得10分。

另20%数据

发现一件事,在比较 s i s_i si s j s_j sj时,假设 i < j i<j i<j,从1到i-1这一段是一样的,从j+1到n是一样的,所以我们只要比较原串的i+1~ j和i ~j-1这两段就行。由于任意相邻两个都不相同,所以能 Θ ( 1 ) \Theta(1) Θ(1)比较。

另30%数据

想到哈希,虽然哈希只能判等,但我们只要二分一下,找出两个字符串的最长公共前缀,再比较最长公共前缀的下一个位置就行。时间复杂度: Θ ( n log ⁡ 2 n ) \Theta(n\log^2n) Θ(nlog2n)

最后40%数据

到这应该很明朗了,两个后缀的最长公共前缀,立刻想到后缀数组。通过 n log ⁡ n n\log n nlogn的预处理,可以 Θ ( 1 ) \Theta(1) Θ(1)比较,再使用 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)的快排就可以了。

实测:TLE 60分

后缀数组组似乎常数太大,不能通过。不过想一想我们需要什么,两个相邻后缀的最长公共前缀,可以设 h i h_i hi为以i和i+1开头的后缀的最长公共前缀的长度,很明显 h i > = h i − 1 − 1 h_i>=h_{i-1}-1 hi>=hi11,其实相当于一起去掉两个开头字符,所以 h i h_i hi一开始就赋值为 h i − 1 − 1 h_{i-1}-1 hi11,就可以 Θ ( n ) \Theta(n) Θ(n)预处理h数组了,最后就可以直接排序了。虽然跑不过众多大佬的 Θ ( n ) \Theta(n) Θ(n)算法,但也能通过了。

code:

#include
using namespace std;
#define cmp(a,b,c) (a+c<=n&&b+c<=n&&y[a]==y[b]&&y[a+c]==y[b+c])
#define min(a,b) (a//卡常
const int maxn=1e6+10;
char s[maxn];
int ans[maxn],h[maxn],n;
inline void write(int x) {//卡常
	if(x>=10)write(x/10);
	putchar(x%10^48);
}
void Swap(int &a,int &b) {//还是卡常
	a^=b,b^=a,a^=b;
}
int cmp1(int a,int b) {//自定义比较方法
	int f=0;
	if(a>b)Swap(a,b),f=1;
	if(h[a]>=b-a)return f^1;
	return (s[a+h[a]+1]<s[a+h[a]])^f;
}
int t[maxn];
void Qsort(int l,int r) {//手写归并排序,也是写后缀数组卡常留下的
	if(l==r)return;
	int mid=(l+r)>>1;
	Qsort(l,mid),Qsort(mid+1,r);
	int i=l,j=mid+1,cnt=0;
	while(i<=mid||j<=r) {
		if((j>r)||(i<=mid&&cmp1(ans[i],ans[j])))t[cnt++]=ans[i++];
		else t[cnt++]=ans[j++];
	}
	for(int i=0; i<cnt; i++)ans[i+l]=t[i];
}
int main() {
	scanf("%d\n",&n);
	fread(s+1,1,n,stdin);//优读,写后缀数组卡常留下的
	for(int i=1; i<n; i++) {//求h数组
		h[i]=max(h[i-1]-1,0);
		for(int j=i+1; s[i+h[i]]==s[j+h[i]]; h[i]++);
	}
	for(int i=1; i<=n; i++)ans[i]=i;//初始化
	Qsort(1,n);
	for(int i=1; i<n; i++)write(ans[i]),putchar(' ');
	write(ans[n]),putchar('\n');
	return 0;
}

你可能感兴趣的:(题解)