1. 程式人生 > >POJ 3693 重複次數最多的連續重複子串 字尾陣列

POJ 3693 重複次數最多的連續重複子串 字尾陣列

題目大意就是求重複次數最多的連續重複子串。例如abababc 答案就是ababab  因為ab連續出現的次數最多

並且題目還要求輸出字典序最小的

比如abababcdcdcd 

ababab和cdcdcd都符合要求

但是ababab字典序小

具體做法參見羅穗騫的論文

窮舉子串的長度L,然後求長度為L的子串最多出現幾次

首先連續出現一次是肯定的,所以只考慮出現兩次及以上的情況

假設在字串中出現了兩次,記這個重複了兩次L長度子串的子串為S。

那麼S肯定包含了字元r[0], r[L], r[L*2], r[3 * L]....中的某相鄰的兩個。

所以就看r[L*i]和r[L*(i + 1)]往前往後分別匹配到多遠,記這個長度為K(具體匹配方式看程式碼),那麼就連續出現了K/L+1次,最後看最大值多少

注意每次求這個k要分為兩種情況,一是公共字首恰好模L等於0,另一種是模L不等於0

模L不等於0時還要計算一個值,假如公共字首%L等於t,就求lcp(i - (L - t), i - (L - t) + L);

為什麼呢,我們畫一畫就知道了,這樣的做法,實際上兩個公共字首往前延伸了幾個位置,使得字首的長度加上延伸的長度是L的倍數

然後求lcp,會發現,他是有可能比原來的k大的,那麼連續出現的次數也有可能改變。所以這種情況不能遺漏

這裡用到了lcp,既求任意兩個字尾的最長公共字首,使用RMQ實現。

最後輸出的時候,由於要按字典序輸出,就列舉sa陣列

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#define MAXN 111111
#define MAXM 200005
#define INF 1000000011
#define lch(x) x<<1
#define rch(x) x<<1|1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define eps 1e-7
using namespace std;
int r[MAXN];
int wa[MAXN], wb[MAXN], wv[MAXN], tmp[MAXN];
int sa[MAXN]; //index range 1~n value range 0~n-1
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, *ws = tmp;
    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 (swap(x, y), 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++;
    }
}
int rank[MAXN]; //index range 0~n-1 value range 1~n
int height[MAXN]; //index from 1   (height[1] = 0)
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);
    return;
}
int Log[MAXN];
int mi[MAXN][20];
void rmqinit(int n)
{
    for(int i = 1; i <= n; i++) mi[i][0] = height[i];
    int m = Log[n];
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++)
        {
            mi[j][i] = mi[j][i - 1];
            if(j + (1 << (i - 1)) <= n) mi[j][i] = min(mi[j][i], mi[j + (1 << (i - 1))][i - 1]);
        }
}
int lcp(int a, int b)
{//詢問a,b字尾的最長公共字首
    a = rank[a];    b = rank[b];
    if(a > b) swap(a,b);
    a ++;
    int t = Log[b - a + 1];
    return min(mi[a][t] , mi[b - (1<<t) + 1][t]);
}
char s[MAXN];
int ans[MAXN];
char out[MAXN];
int main()
{
    Log[1] = 0;
    for(int i = 2; i < MAXN; i++) Log[i] = Log[i >> 1] + 1;
    int cas = 0;
    while(scanf("%s", s) != EOF && strcmp(s, "#") != 0)
    {
        int n = strlen(s), m = 0;
        for(int i = 0; i < n; i++)
        {
            m = max(m, (int)s[i]);
            r[i] = s[i];
        }
        r[n] = 0;
        da(r, sa, n + 1, m + 1);
        calheight(r, sa, n);
        rmqinit(n);
        int mx = -1;
        int cnt, l;
        for(l = 1; l < n; l++) //列舉子串的長度
        {
            for(int i = 0; i + l < n; i += l)
            {
                int k = lcp(i, i + l);//計算r[L*i]和r[L*(i+1)]的最長公共字首
                int p = k / l + 1;
                int t = l - k % l;
                t = i - t;
                //printf("ss i :%d i + l :%d t :%d t + l:%d p: %d\n", i, i + l, t, t + l, p);
                if (t >= 0 && k % l != 0) 
                {
                    int tk = lcp(t, t + l);
                    if(tk / l + 1 > p) p = tk / l + 1;
                }
                if(p > mx)
                {
                    cnt = 0;
                    mx = p;
                    ans[cnt++] = l;
                }
                if(p == mx) ans[cnt++] = l;
            }
        }
        int pos = 0;
        int flag = 0;
        for(int i = 1; i <= n && !flag; i++)
        {
            for(int j = 0; j < cnt; j++)
            {
                int k = ans[j];
                if(lcp(sa[i], sa[i] + k) >= (mx - 1) * k)
                {
                    pos = sa[i];
                    l = mx * k;
                    flag = 1;
                    break;
                }
            }
        }
        printf("Case %d: ", ++cas);
        for(int i = 0; i < l; i++) printf("%c", s[pos + i]);
        printf("\n");
    }
    return 0;
}