5442 Favorite Donut 最大表示法+KMP || 字尾陣列
阿新 • • 發佈:2019-02-09
題意:
有一個由小寫字母組成的字串(長度為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; }