老實說跑過來寫博客是很慚愧的…畢竟沒有靠自己的能力解決這道題…代碼的關鍵部分也是借鑑了同學的,但是畢竟還是花了我3個小時的時間來調試,所以還是來稍微記錄一下以免以後再忘記。
本週的學習內容是傳說中的後綴數組,本來是試圖弄明白後綴數組是怎麼求出來的,在研究兩位大神的論文無果之後,決定還是退而求其次研究研究後綴數組怎麼用,模板什麼的大不了下功夫先背下來等以後有了更深的體會以後再拿來研究研究。
後綴數組就是把某一個串的全部後綴拿出來比較然後對它們進行一次排序,排序的結果存在我們的sa數組裏面,簡單來說,sa[i]裏面存的就是排名第i的後綴的第一個字符在字符串中的位置
與之對應的就是rank數組,rank[i]存儲的是以第i個字符爲開頭的後綴在我們後綴數組中的排名。
另外一個比較重要的應用就是用後綴數組求height數組,height[i]存儲的是後綴數組中排名第i-1和排名第i的後綴的最長公共前綴的長度,應用height數組我們可以來解決最長重複子序列的問題。
本題的本質就是求n個字符串中,重複次數超過k個的最長子串,所以我們首先要解決的問題就是如何求n個字符串的公共子串。
一個經典的解決方案是把這n個字符串全部鏈接起來,每兩個字符串之間用一個字符串中未曾出現的字符進行間隔,而且所有的間隔符也是兩兩不同的,這樣才能保證我們在求後綴數組的時候,不同的字符串之間不相互干擾。
求出height數組之後,如果在height的一片連續的區域height[sa[i]..sa[j]]內的值都相等,那麼說明sa[i]…sa[j]代表的後綴具有相同的前綴,即具有重複的字串。
查找sa[i]…sa[j]原始分別在那些字符串裏面即可得到該字符串的重複度,如果,在本題之中,字符串的重複度只要大於n/2就可以輸出。
爲了得到sa[i]代表的字符在哪個原始字符串中,設立了st_en[n][2]數組來存儲每個字符串的起始位置和結束位置(這裏應該是隻記錄每個字符串的結束位置就可以判斷了,但是爲了直觀還是把起始地址和結束地址都記錄下來)
爲了找到我們所求的最長的滿足要求的字符串,我們先確定一個字符串長度的最大值,即每個字符串長度的最大值,再確定一個字符串長度的最小值,即爲0,之後的一個最直接的思路是對長度從大到小進行枚舉,找到最大的符合要求的字符串長度,然後輸出找到的字符串,但是這樣效率肯定是比較低的,所以這裏採用了二分的方法,從上界和下界的平均值開始找,折半縮小查找範圍。
寫的時候把sa數組變成局部變量,崩的心碎,調成全局變量就好了…也不是很懂爲什麼,下一步還是要多練習強化,寒假決定多刷幾道題!
#include<cstdio>
#include<stdlib.h>
#include<string.h>
using namespace std;
#define maxn 100250
#define maxc 115
#define maxl 1105
int ws[maxn]={0}, wv[maxn]={0}, wa[maxn]={0}, wb[maxn]={0};
int rank[maxn]={0}, height[maxn]={0};
char data[maxc][maxl]={0};
int link_num[maxn]={0};
int len[maxn]={0};
int st_en[maxc][2]={0};
int sa[maxn]={0};
int cmp(int *r, int a, int b, int l){
return (r[a] == r[b]) && (r[a + l] == r[b + l]);
}
//計算後綴數組的模板
void DA(int *r, int *sa, int n, int m) {
int i, j, p, *x = wa, *y = wb, *t;
for (i = 0; i<m; i++) ws[i] = 0;
for (i = 0; i<n; i++) ws[x[i] = r[i]]++;
for (i = 1; i<m; i++) ws[i] += ws[i - 1];
for (i = n - 1; i >= 0; i--) sa[--ws[x[i]]] = i;
for (j = 1, p = 1; p<n; j *= 2, m = p){
for (p = 0, 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<n; i++) wv[i] = x[y[i]];
for (i = 0; i<m; i++) ws[i] = 0;
for (i = 0; i<n; i++) ws[wv[i]]++;
for (i = 1; i<m; i++) ws[i] += ws[i - 1];
for (i = n - 1; i >= 0; i--) sa[--ws[wv[i]]] = y[i];
for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i<n; i++)
x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
}
}
//計算height數組模板
void calheight(int *r, int *sa, int n) {
int i, j, k = 0;
for (i = 1; i <= n; i++) rank[sa[i]] = i;
for (i = 0; i<n; height[rank[i++]] = k)
for (k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++);
}
//通過某個數字在鏈接起來的數組裏面的位置來判斷這個數字是在初始的哪個字符串裏面的
int getID(int k,int n){
for(int i=0;i<n;++i)
if(k>=st_en[i][0]&&k<=st_en[i][1])
return i;
return -1;
}
//判斷是否有超過一半的字符串含有我們找到的字符串
bool check(int mid,int *h,int tcount,int n, bool output = false){
int i=0,cnt = 0;
while(true){
bool visited[maxc]={0};
while(i<tcount && height[i]<mid)
++i;
if(i>=tcount)
break;
visited[getID(sa[i-1],n)] = true;
cnt = 1;
while(i<tcount&&height[i]>=mid){
if(!visited[getID(sa[i],n)]){
visited[getID(sa[i],n)] = true;
cnt++;
}
++i;
}
if((cnt>n/2)&&output){
for(int j=sa[i-1];j<sa[i-1]+mid;j++)
printf("%c",link_num[j]+'a'-maxc);
printf("\n");
}
else if(cnt>n/2)
return true;
}
return false;
}
int main(){
int n;
while(true){
memset(sa,0,sizeof(sa));
memset(height,0,sizeof(height));
memset(rank,0,sizeof(rank));
int maxlen = 0;
scanf("%d",&n);
if(!n)
break;
for(int i=0;i<n;++i){
scanf("%s",data[i]);
len[i] = strlen(data[i]);
if(len[i]>maxlen)
maxlen = len[i];
}
if(n==1){
printf("%s\n\n",data[0]);
continue;
}
int tcount = 0;
for(int i=0;i<n;++i){
st_en[i][0] = tcount;
for(int j=0;j<len[i];++j){
link_num[tcount++] = (int)(data[i][j]-'a')+maxc;
}
st_en[i][1] = tcount;
link_num[tcount++] = i;
}
DA(link_num,sa,tcount,200);
calheight(link_num,sa,tcount-1);
/*for(int i=0;i<tcount-1;++i){
printf("%d ",height[i]);
}
printf("\n");*/
int left = 0,right =maxlen+1;
while(left<right){
//printf("%d %d\n",left,right);
int mid = (left+right+1)/2;
if(check(mid,height,tcount,n)){
left = mid;
}
else
right = mid-1;
}
if(!left){
printf("?\n\n");
}
else{
check(left,height,tcount,n,true);
printf("\n");
}
}
return 0;
}