1. 程式人生 > 實用技巧 >[題解][筆記]字尾陣列&重複旋律1

[題解][筆記]字尾陣列&重複旋律1

[題解][筆記]字尾陣列&重複旋律1

暫時先引用YYL神佬的部落格

注意

這篇部落格比較沒用,主要是記錄一下思路

先放程式碼

#include <bits/stdc++.h>
using namespace std;
struct Rank{
	int key1,key2,id;
}ss[1000050];
int rk[1000050],sa[1000050],height[1000050],tmp[1000050],n,k,rkk;
bool cmp(Rank x,Rank y){
	if(x.key1 == y.key1)return x.key2 < y.key2;
	return x.key1 < y.key1;
}
bool check(int mid){
	int cnt = 1,size = 1;
	for(int i = 2;i <= n;i++){
		if(height[i] < mid)cnt = max(cnt,size),size = 1;//統計是否有連續的m-1個height陣列中的元素大於mid
		else size++;
	}
	return cnt >= k;
}
void get_height(){
	int k = 0;
	for(int i = 1;i <= n;i++)rk[sa[i]] = i;
	for(int i = 1;i <= n;i++){//算height
		if(k)k--;//因為height[i]≥height[i-1]-1,所以不用從0開始,否則效率會大大降低
		int j = sa[rk[i] - 1];
		while(tmp[i + k] == tmp[j + k])//暴力匹配
			k++;
		height[rk[i]] = k;
	}
}
void init_rank(){
	rkk = 1;
	rk[ss[1].id] = 1;
	for(int i = 2;i <= n;i++){
		if(ss[i].key1 != ss[i - 1].key1 || ss[i - 1].key2 != ss[i].key2)
			rkk++;
		rk[ss[i].id] = rkk;
	}
}
void get_rank(){
	sort(ss + 1,ss + n + 1,cmp);
	init_rank();
	int x = 1;
	for(int i = 1;i <= n;i <<= 1){//倍增求rank
		for(int j = 1;j <= n;j++){
			ss[j].key1 = rk[j];ss[j].id = j;
			if(i + j > n)
				ss[j].key2 = 0;
			else 
				ss[j].key2 = rk[j + i];
		}
		sort(ss + 1,ss + n + 1,cmp);
		init_rank();
		if(rkk == n)//所有元素都被排好序了就可以退出了
			break;
	}
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;i++){
		scanf("%d",&ss[i].key1);
		ss[i].key2 = ss[i].key1;
		tmp[i] = ss[i].key1;
		ss[i].id = i;
	}
	get_rank();
	for(int i = 1;i <= n;i++)sa[rk[i]] = i;
	get_height();
	int l = 1,r = n,ans = 0;
	while(l <= r){
		int mid = (l + r) / 2;
		if(check(mid))
			ans = mid,l = mid + 1;
		else r = mid - 1;
	}
	printf("%d\n",ans);
	return 0;
}

大部分程式碼都有註釋,現在就重點講一下二分求答案的部分

首先我們知道這個二分統計的是$height$陣列中大小大於$mid$的元素是否有$k-1$個,$mid$是直接二分的答案,也就是長度,那為什麼可以這麼二分呢?因為$height$陣列是相鄰字尾的最長相同字首,又因為是兩兩比較,所以是和$k-1$比較,通過上文對$height$陣列的講解,其實我們可以發現,這個二分的過程就是求出第$x$個字尾和第$x+k-1$個字尾的最長相同字首,那麼這個字首就是在字串中出現了$k$次的滿足條件的答案.