1. 程式人生 > >5442 Favorite Donut 最大表示法+KMP || 字尾陣列

5442 Favorite Donut 最大表示法+KMP || 字尾陣列

題意:

有一個由小寫字母組成的字串(長度為n),首尾相接,求順時針轉和逆時針轉的情況下,長度為n的最大字典序的字串的首位的位置。

如果順時針和逆時針求得的字串相同,則選擇開始位置較前的,如果開始位置也相同,則選擇順時針的。

如abcd,那麼順時針可以是abcd,bcda,cdab,dabc.逆時針可以是adcb,dcba,cbad,badc.

思路:

這個題目可以用最大表示法來做,對於順時針我們可以直接做一遍最大表示法,返回的就是最小的下標.

對於逆時針的情況,我們將串反轉然後跑一遍最大表示法,但是注意這樣返回的最小下標因為逆序了,在原串中是最大的.我們還要求的逆序的最小怎麼辦呢?KMP.

我們將串逆序存兩遍,用最大表示法求得的字典序最大的串去匹配,這個過程標記匹配的位置,然後維護一個最大值(對應原串中最小的下標).

具體見程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e5+10;
char s[maxn],rs[maxn];
int nxt[maxn];
char ss[maxn],sp[maxn];
int id[maxn];
void get_nxt(char *b,int len)    
{    
    //int len = strlen(b);  
    nxt[0] = -1;    
    int i = 0,j = -1;    
    while(i < len)    
    {    
        if(j == -1 || b[i] == b[j])    
        nxt[++i] = ++j;    
        else    
        j = nxt[j];    
    }    
 } 
int get_ma(char *b,int l)      
{      
    int i = 0, j = 1, k = 0, t;      
    while(i < l && j < l && k < l) {      
        t = b[(i + k) >= l ? i + k - l : i + k] - b[(j + k) >= l ? j + k - l : j + k];      
        if(!t) k++;      
        else{      
            if(t < 0) i = i + k + 1;      
            else j = j + k + 1;      
            if(i == j) ++ j;      
            k = 0;      
        }      
    }      
    return (i < j ? i : j);      
}      
void KMP(char *a,char *b)    
{    
    int i=0,j=0; 
    int lena = strlen(a);
    int lenb = strlen(b);
    while(i<lena)   
    {    
        if(j==-1||a[i] == b[j])    
        {    
        //puts("2");
            if(j==lenb-1)  
            {    
                id[i-lenb+1] = 1;   
                j=nxt[j]; 
            }    
            else    
            {    
                i++,j++;    
            }    
        }    
        else    
        j=nxt[j];    
    }    
    //puts("---");
    return ;    
} 
int main(){
	int _;
	cin>>_;
	int n ;
	while(_--)
	{
		scanf("%d",&n);
		scanf(" %s",s);
		memset(rs,0,sizeof rs);
		for(int i = 0;i < n ;++i)
		rs[n - i - 1] = s[i];
		int id1 = get_ma(s,n);
		int id2 = get_ma(rs,n);
		for(int i = 0;i < 2 * n ;++i)
		{
			ss[i] = s[n - i%n - 1];
		}
		s[2*n] = 0;
		for(int i = 0;i < n;++i)
		sp[i] = rs[(id2+i) % n];
		sp[n] = 0;
		get_nxt(sp,n);
		//for(int i = 0;i < n;++i)
		//cout<<nxt[i]<<endl;
		memset(id,0,sizeof id);
		KMP(ss,sp);
		for(int i = n - 1;i >= 0;--i)
		{
			if(id[i])
			{
				id2 = i;
				break;
			}
		}
		int index = 1;
		bool same = 1;
		for(int i = 0;i < n;++i)
		{
			if(s[(id1+i)%n] == rs[(id2+i)%n]) continue;
			else if(s[(id1+i) % n] > rs[(id2+i)%n]) 
			{
				same = 0,index = 1;	break;
			}
			else
			{
				same = 0,index = 2;break;
			}
		}
		int ii = id1+1;
		int jj = n - id2 ;
		if(same)
		{
			if(ii <= jj)
			printf("%d %d\n",ii,0);
			else
			printf("%d %d\n",jj,1);
		}
		else
		{
			if(index == 1)
			printf("%d %d\n",ii,0);
			else
			printf("%d %d\n",jj,1);
		}
	} 
	 
	return 0;
}


當然這個題目也可以用字尾陣列來解決.

字尾陣列嘛我們就是對每個字尾進行字典序排序,那麼我們將字串正序存兩遍,(因為迴圈)再把字串逆序存兩次.但是二者之間需要加一個其他字元,注意這個其他字元不能影響二者的排序,這裡選擇‘#’.

SA[i]表示排名第i的是哪個字尾.我們可以根據height陣列求得的最長公共字首LCP,來判斷這個串是否符合條件.height[i] 表示suff[SA[I]]和suff[sa[i-1]的LCP,由於我們的串是可以迴圈的所以這裡我們必須要保證height陣列當中的串LCP>=n這樣才能保證我們得到的才是符合我們題意的長度為n的字串。

#include<bits/stdc++.h>

using namespace std;
const int maxn = 2e5+5;
typedef long long ll;
char s[maxn];
int t1[maxn],t2[maxn],c[maxn];
int ra[maxn],height[maxn],sa[maxn];
char str[maxn];
int n;
bool cmp(int *r, int a, int b, int l)    
{    
    return r[a]==r[b]&&r[a+l]==r[b+l];    
}    
    
void da(char str[], int sa[], int ra[], int height[], int n, int m)    
{    
    n++;    
    int i, j, p, *x = t1, *y = t2;    
    for(i = 0; i < m; i++) c[i] = 0;    
    for(i = 0; i < n; i++) c[x[i]=str[i]]++;    
    for(i = 1; i < m; i++) c[i] += c[i-1];    
    for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;    
    for(j = 1; j <= n; j<<=1)    
    {    
        p = 0;    
        for(i = n-j; i < n; i++) y[p++] = i;    
        for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;    
        for(i = 0; i < m; i++) c[i] = 0;    
        for(i = 0; i < n; i++) c[x[y[i]]]++;    
        for(i = 1; i < m; i++) c[i] += c[i-1];    
        for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];    
        swap(x, y);    
        p = 1; x[sa[0]] = 0;    
        for(i = 1; i < n; i++)    
            x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;    
        if(p >= n) break;    
        m = p;    
    }    
    int k = 0;    
    n--;    
    for(i = 0; i <= n; i++) ra[sa[i]] = i;    
    for(i = 0; i < n; i++)    
    {    
        if(k) k--;    
        j = sa[ra[i]-1];    
        while(str[i+k]==str[j+k]) k++;    
        height[ra[i]] = k;    
    }    
}    
struct node
{
	int p,c;
	bool operator<(const node & a) const
	{
    	if(a.p == p) return c < a.c;
    	return p < a.p;
	}
}ans[maxn];


int main()
{
	int _;
	cin>>_;
	while(_--)
	{
		scanf("%d",&n);
		scanf("%s",s);
		for(int i = 0;i < n;++i)
		str[i] = str[i+n] = s[i];
		int len = n << 2 | 1;
		str[n << 1] = '#';
		for(int i = 0;i < n;++i)
		str[i+2*n+1] = str[i+3*n+1] = str[n-i-1];
		str[len] = 0;
		da(str,sa,ra,height,len,328);
		int cnt = 0;
		ans[cnt++].p = sa[len];
		for(int i = len - 1;i > 1;--i)
		{
			if(height[i+1] < n) break;
			if(sa[i] >= n &&sa[i] <= 2*n) continue;//無法迴圈
			if(sa[i] >= 3*n+1) continue;//無法迴圈
			ans[cnt++].p = sa[i];
		}
		for(int i = 0;i < cnt;++i)
		{
			if(ans[i].p > 2*n)
			{
				ans[i].c = 1;
				ans[i].p = n - (ans[i].p-2*n-1);
			}
			else
			{
				ans[i].p++;
				ans[i].c = 0;
			}
		}
		sort(ans,ans+cnt);
		printf("%d %d\n",ans[0].p,ans[0].c);
	}
	return 0;
}