Musical Theme POJ - 1743(后缀数组+二分)

题目链接
题意:
有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:
1.长度至少为5个音符。
2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
3.重复出现的同一主题在原序列中不能有重叠部分。
题目思路:
对于转调,因为a[i]-a[i-1]还是不变,所以我们可以构造a[i]-a[i-1]的后缀数组,从而用寻找最长不重叠子串的方法来写这个题。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define Max_N 20010
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2) 
int wa1[Max_N*3], wb1[Max_N*3], wv1[Max_N*3], ws1[Max_N*3]; 
int c0(int *r, int a, int b)  
{
	return r[a] == r[b] && r[a+1] == r[b+1] && r[a+2] == r[b+2];
}
 
int c12(int k, int *r, int a, int b) 
{
	if(k == 2) return r[a] < r[b] || r[a] == r[b] && c12(1, r, a+1, b+1); 
    else return r[a] < r[b] || r[a] == r[b] && wv1[a+1] < wv1[b+1];
}
    
 void sort(int *r, int *a, int *b, int n, int m) 
 { 
	int i; 
	for (i = 0; i < n; i++) wv1[i] = r[a[i]]; 
	for (i = 0; i < m; i++) ws1[i] = 0; 
	for (i = 0; i < n; i++) ws1[wv1[i]]++; 
	for (i = 1; i < m; i++) ws1[i] += ws1[i-1]; 
	for (i = n-1; i >= 0; i--) b[--ws1[wv1[i]]] = a[i]; 
	return; 
 }
void dc3(int *r, int *sa, int n, int m)
{
	int i, j, *rn = r + n, *san = sa + n,ta = 0,tb = (n + 1) / 3,tbc = 0,p; 
    r[n] = r[n+1] = 0; 
    for (i = 0; i < n; i++) if (i % 3 != 0) wa1[tbc++] = i; 
    sort(r+2, wa1, wb1, tbc, m); 
    sort(r+1, wb1, wa1, tbc, m); 
    sort(r, wa1, wb1, tbc, m); 
    for (p = 1,rn[F(wb1[0])] = 0,i = 1;i < tbc; i++) 
    rn[F(wb1[i])] = c0(r, wb1[i-1], wb1[i]) ? p - 1 : p++; 
    if (p < tbc) dc3(rn, san, tbc, p); 
    else for (i = 0; i < tbc; i++) san[rn[i]] = i;
    for (i = 0; i < tbc; i++) if (san[i] < tb) wb1[ta++] = san[i] * 3; 
    if (n % 3 == 1) wb1[ta++] = n - 1; 
    sort(r, wb1, wa1, ta, m); 
    for (i = 0; i < tbc; i++) wv1[wb1[i] = G(san[i])] = i; 
    for (i = 0, j = 0, p = 0; i < ta && j < tbc; p++) 
    sa[p] = c12(wb1[j] % 3, r, wa1[i], wb1[j]) ? wa1[i++] : wb1[j++]; 
    for(; i < ta; p++) sa[p] = wa1[i++]; 
    for(; j < tbc; p++) sa[p] = wb1[j++]; 
    return; 
}
// str sa 都要开三倍的;
void da(int str[], int sa[], int rank1[], int height1[], int n, int m)
{
	for (int i = n; i < n*3; i++)
		str[i] = 0;
	dc3(str, sa, n+1, m);
	int i, j, k = 0;
	for (i = 0; i <= n; i++) rank1[sa[i]] = i;
	for (i = 0; i < n; i++)
	{
		if (k) k--;
		j = sa[rank1[i] - 1];
		while (str[i+k] == str[j+k]) k++;
		height1[rank1[i]] = k;
	}
	return;
}
int a[Max_N];
int str[3*Max_N];
int sa[3*Max_N];//sa[i]表示将所有后缀排序后第i小的后缀的编号。
int rank1[3*Max_N];//rank1[i]表示后缀i的排名。
int height1[3*Max_N]; //LCP(sa[i],sa[i-1]);
int n;
bool judge(int c)
{
	int Max=sa[0],Min=sa[0];
	for(int i=1;i<=n;i++)
	{
		if(height1[i]>=c)
            Max=max(Max,sa[i]),Min=min(Min,sa[i]);
        else
            Max=sa[i],Min=sa[i];
        if(Max-Min>=c+1)
            return true;
	}
	return false;
}
int main()
{
	while(~scanf("%d",&n))
	{
		if(n==0)
		break;
		int m=0;
		str[0]=0;
		for(int i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
			if(i)
			{
				str[i-1]=a[i]-a[i-1]+100;
				m=max(m,str[i-1]);
			}
		}
		n--;
		m+=10;
		str[n]=0;
		da(str, sa, rank1, height1, n, m);
		int L=0;
		int R=n;
		int ans=0;
	/*	for(int i=0;i<=n;i++)
		{
			printf("%d ",height1[i]);
		}
		printf("\n");*/
		while(L<=R)
		{
			int mid=(L+R)/2;
			if(judge(mid))
			{
				L=mid+1;
				ans=max(ans,mid);
			}
			else
			{
				R=mid-1;
			}
		}
		if(ans>=4)
		{
			printf("%d\n",ans+1);
		}
		else
		{
			printf("0\n");
		}
	}
	
}
posted @ 2020-10-13 16:50  Ldler  Views(57)  Comments(0Edit  收藏  举报