1. 程式人生 > 實用技巧 >POJ-1743 Musical Theme 字尾陣列求不可重疊最長重複子串

POJ-1743 Musical Theme 字尾陣列求不可重疊最長重複子串

POJ-1743 Musical Theme 字尾陣列求不可重疊最長重複子串

題意

給出一段樂曲,計算其中最長“主題”的長度

主題需要滿足

  • 長度至少為5個字元
  • 在樂曲中重複出現(可能經過轉調)
  • 重複出現的同一主題不能有公共部分

所謂“轉調”,是指每個音符都被加上或者減去同個值。

樂曲中的每個音符都是1到88的整數

給出N,表示這段樂曲有N個音符,後給出整數

\[1\leq n\leq 20000 \]

分析

首先轉調,可以轉化為差分做,再把整個樂曲看成一個字串,問題就變成了字串中不可重疊的最長重複子串,這個問題在羅老師的經典論文中已經詳細說明,這裡說幾個注意點

  • 需要特判1
  • 處理這類問題通常用int代替char
  • 用劉汝佳老師的模板,通常需要加入一個'$',若是int,則是0
  • 將問題轉化為判定性問題,分組 是處理字尾陣列問題的常用手段

程式碼

int s[maxn],p[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn],n;

void build_sa(int m){
	int *x = t,*y = t2;
	for(int i = 0;i < m;i++) c[i] = 0;
	for(int i = 0;i < n;i++) c[x[i] = s[i]]++;
	for(int i = 1;i < m;i++) c[i] += c[i -1];
	for(int i = n - 1;i >= 0;i--) sa[--c[x[i]]] = i;
	for(int k = 1;k <= n;k <<= 1){
		int p = 0;
		for(int i = n - k;i < n;i++) y[p++] = i;
		for(int i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k;
		for(int i = 0;i < m;i++) c[i] = 0;
		for(int i = 0;i < n;i++) c[x[y[i]]]++;
		for(int i = 0;i < m;i++) c[i] += c[i - 1];
		for(int i = n - 1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];
		swap(x,y);
		p = 1;
		x[sa[0]] = 0;
		for(int i = 1;i < n;i++)
			x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p - 1:p++;
		if(p >= n) break;
		m = p;
	}
}

int rak[maxn],height[maxn];

void getHeight(){
	int k = 0;
	for(int i = 0;i < n;i++) rak[sa[i]] = i;
	for(int i = 0;i < n;i++){
		if(k) k--;
		int j = sa[rak[i] - 1];
		while(s[i + k] == s[j + k]) k++;
		height[rak[i]] = k;
	}
}

bool check(int len){
	int mx = sa[0],mi = sa[0];
	for(int i = 1;i < n;i++){
		if(height[i] >= len - 1) 
			mx = max(sa[i],mx),mi = min(sa[i],mi);
		else 
			mx = mi = sa[i];
		if(mx - mi >= len) return true;
	}
	return false;
}

int solve(){
	int l = 0,r = n,ans = 0;
	while(l <= r) {
		int mid = l + r  >> 1;
		if(check(mid)) l = mid + 1,ans = mid;
		else r = mid - 1;
	}
	return ans;
}

int main(){
	while(~scanf("%d",&n) && n){
		for(int i = 0;i < n;i++) p[i] = readint();
		if(n == 1) {
			puts("0");
			continue;
		}
		for(int i = 0;i < n - 1;i++) s[i] = p[i + 1] - p[i] + 100;
		s[n - 1] = 0;
		build_sa(200);
		getHeight();
		int ans = solve();
		if(ans >= 5) printf("%d\n",ans);
		else puts("0");
	}
	
}