sa learning

后缀数组之前一直在给队友搞,但是这个类太大了,预感到青岛八成会有,于是自己也学习一下,记录一下做题的历程

所用的模板暂时来自于队友的倍增nlogn da算法

int t1[maxn] , t2[maxn] , c[maxn] ;

bool cmp(int *r , int a , int b , int l) {
    return r[a] == r[b] && r[a+l] == r[b+l] ;
}

void da(int s[], int sa[], int ra[], int he[], 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] = s[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(s[i+k] == s[j+k]) {
            k ++ ;
            //printf("%d %d %d
" , i,j,k ) ;
        }
        he[ra[i]] = k ;
    }
}

int ra[maxn] , he[maxn] ;
int rmq[maxn] ;
int mm[maxn] ;
int min1[maxn][25] ;
void init_rmq(int n) {
    memset(min1, 0 , sizeof(min1)) ;
    for(int i = 1 ; i <= n ; i ++ ) min1[i][0] = he[i] ;
    int l = int(log((double)n)/log(2.0)) ;
    for(int j = 1 ; j <= l ; j ++ ) {
        for(int i = 1 ; i + (1<<j-1) - 1 <= n ; i ++ ) {
            min1[i][j] = min(min1[i][j-1] , min1[i+(1<<(j-1))][j-1]) ;
        }
    }
}
int ask(int l,int r) {
    int k = int(log(double(r-l+1))/log(2.0));
    return min(min1[l][k], min1[r-(1<<k) + 1][k]) ;
}
int lcp(int a,int b) {
    int ar = ra[a] , br = ra[b] ;
    if (ar > br) swap(ar,br) ;
    return ask(ar+1,br) ;
}

int s[maxn] ;
int sa[maxn] ;

敲错模板是个很头疼的事情,耗费了许多时间

SPOJ - DISUBSTR

求不同子串数目

子串一定是一个后缀的前缀,进行字符串排序后,相邻rank的两个字符串一定有共同的前缀(即使长度为0),所以在统计前缀的时候,对于相同的前缀,永远只统计后者在前者中未出现的前缀,因为前者的独有前缀也将在其作为后者时被计算到。

当然这其实就是height数组的概念。。

char S[maxn] ;

int main () {
    int t ;
    scanf("%d" , &t) ;
    while(t -- ) {
        scanf("%s" , S) ;
        int n = strlen(S) ;
        for(int i = 0 ; i < n; i ++ ) {
            s[i] = int(S[i]) ;
        }
        s[n] = 0 ;
        da(s,sa,ra,he,n,256);
        for(int i=1;i<=n;i++)rmq[i] = he[i];
        init_rmq(n) ;
        int ans = 0 ;
        for(int i=1;i<=n;i++){
            int pos1 = sa[i] ;
            int pos2 = sa[i-1] ;
            int lc = lcp(pos1,pos2);
            int len=n-(pos1 + lc);
            ans += len ;
        }
        printf("%d
" , ans) ;
    }
}

 POJ - 3261 

求出现至少k次的可重复子串

网上的题解多为二分ans,然后对height中施加限制,查看是否有k个,实际上不需这样做

我们认为子串一定是后缀的前缀,那么排序后缀后,相等的子串所代表的后缀一定是相邻的,所以对于排名为x的后缀,只需要寻找排名为x-k+1的后缀,然后看看它们的lcp就可以了

不过复杂度实际上没有二分ans快,因为预处理rmq其实也是nlogn的,甚至还会慢一些。

当子串元素过大的时候会跑的很慢,据说这个题虽然元素的大小为1e6但是实际上没有,可以通过离散化来将m降低到n的大小

int main () {
    int n , k ;
    while(scanf("%d%d" , &n,&k) != EOF) {
        rep(i,0,n-1) scanf("%d" , &s[i]) ;
        s[n] = 0 ;
        da(s,sa,ra,he,n,1000000) ;
        init_rmq(n);
        int ans = 0 ;

        rep(i,1,n) {
            int pos1 = sa[i] ;
            if(i-k+1 < 1) continue ;
            int pos2 = sa[i-k+1] ;
            ans = max(ans , lcp(pos1,pos2)) ;
        }
        printf("%d
" , ans) ;
    }
}

 POJ1743

求不重叠且出现多次的子串的最长长度

对于转置,我们可以保存差分,这样就把同类串给归类了

如果题目不要求不重叠,那么想必ans就是最大的height了,因为排序后相邻rank的字符串必定是最相似前缀

要求不重叠的话,由于LCP是rank相差越大越小,所以有资质满足lcp>k的串其实就那么一个区间,因为有些串因为相邻太近所以其实没有lcp那么大,所以需要特殊对待

这样其实就是n2的复杂度,对于每个后缀,二分需找他的lcp>k的区间,然后暴力check可以得到的重复子串的长度 然后t了

学习了一下二分ans的写法,感觉很酷,当我们二分出来ans之后,可以对height进行“分类”,由于我们知道lcp必然是大于我们这个ans的,而一段height的最小值必须要大于等于ans

所以我们可以维护这个height连续>=二分ans的区间的pos的最大值和最小值,在这个区间无法维护时,也就是当前我们认为可能构成答案的子串的rank区间已经得出时,重置最大最小值。

其实写起来代码量还是很少的。。

bool check(int len) {
    int minn = 999999999 ;
    int maxx = -999999999 ;
    for(int i = 1 ; i <= n ; i ++ ) {
        if(he[i] >= len) {
            minn = min(minn,min(sa[i],sa[i-1])) ;
            maxx = max(maxx,max(sa[i],sa[i-1])) ;
            if(minn + len  < maxx) {
                return true;
            }
        }
        else {
            minn = 999999999 ;
            maxx = -999999999 ;
        }
    }
    return false ;
}

int main () {
    while(scanf("%d" , &n) != EOF) {
        if( n == 0 ) break;
        rep(i,0,n-1) scanf("%d" , &s[i]) ;
        rep(i,0,n-1) ss[i] = s[i] ;
        for(int i = n - 1 ; i >= 1 ; i -- ) s[i] = s[i] - s[i-1] + 95 ;
        s[n] = 1 ;
        s[0] = 2 ;
        da(s , sa,ra,he,n,200) ;
        init_rmq(n) ;
        int res = -1 ;
        int l = 0 , r = n ;
        while(l <= r) {
            int mid = (l + r) / 2 ;
            if (check(mid)) {
                res = mid ;
                l = mid + 1 ;
            }
            else {
                r = mid - 1 ;
            }
        }
        if(res < 4) printf("0
") ;
        else printf("%d
" , res+1) ;
    }
}

POJ2406

判断一个串是否由一个串多次重复而来

做法是对串长度n求其约数, sqrt(n),然后暴力判断其与我们想象的最后一段求lcp,这个lcp应当是我们想出来的子串长度,把所有可能的子串尝试一遍。

因为串长1e6,所以直接上nlogn的da会T,虽然我get到的是mle...据说是dc3,留待之后有时间写一下,zls号称自己写这个板子无敌,我信了。

POJ2774

求两个字符串的最长公共子串 长度为1e5,不可dp

做法是将两个字符串拼接起来,中间用个分隔符,按照ans和height分类,同一类中只要同时存在左右两个串的子串就ok

运用到两个思想 分类height和拼接两个字符串,前者避免了我们去寻找height区间和check,后者使得我们可以将两个串的sa放在一起考虑,处理多串有用

int n , m ;
int pren ;

bool check(int x) {
    bool A = false , B = false ;
    for(int i = 1 ; i <= n ; i ++ ) {
        if(he[i] >= x) {
            int pos1 = sa[i] ;
            int pos2 = sa[i-1] ;
            if(pos1 < pren) A = true ;
            else B = true ;
            if(pos2 < pren) A = true ;
            else B = true ;
            if(A && B) return true ;
        }
        else {
            A = B = false ;
        }
    }
    return false ;
}

int main () {
    scanf("%s" , S) ;
    n = strlen(S) ;
    pren = n ;
    for(int i = 0 ; i < n ; i ++ ) s[i] = S[i] - 'a' + 1 ;
    s[n] = 27 ;
    scanf("%s" , S) ;
    int n2 = strlen(S) ;
    for(int i = 0 ; i < n2 ; i ++ ) s[n+i+1] = S[i] - 'a' + 1 ;
    n += (n2 + 1) ;

    s[n] = 0 ;

    da(s,sa,ra,he,n,28) ;
    int l = 0 , r = n2 ;
    int res = 0 ;
    while(l <= r) {
        int mid = (l + r) / 2 ;
        if (check(mid)) {
            res = mid ;
            l = mid + 1 ;
        }
        else {
            r = mid - 1 ;
        }
    }
    printf("%d
" , res) ;
}

 

POJ3294

求在n个串中至少k个串的最长公共子串

n个串连接,排序后二分ans并进行分组,对同组check是否够k个了,因为要求记录ans,因为这一组都满足,所以ans直接选i就可以了

由于连接两个串的时候,我们使用一个未曾出现过的字符做分隔符,是为了避免abbbbc#aacaa这种情况,如果不加c就会影响我们的判断,导致ans=caa,那么如果多个串相连,我们每次都需要采用未曾出现过的字符做分隔符。

void flushans(){
    ansnum = M ;
    for(int i = 0 ; i < M ; i ++ ) ans[i] = tmp[i] ;
}

int getpos(int pos) {
    for(int i = 1 ; i <= N-1 ; i ++ ) {
        if(pos > endpos[i] && pos <= endpos[i+1]) return i+1 ;
    }
    return 1 ;
}

bool check(int mid) {
    M = 0 ;
    int mp[1005] ;
    memset(mp,0,sizeof(mp)) ;
    int cnt = 0 ;
    for(int i = 1 ; i <= n ; i ++ ) {
        int pos = i ;
        cnt = 0 ;
        while(pos <= n && he[pos] >= mid) {
            int pos1 = sa[pos] ;
            int pos2 = sa[pos-1] ;
            int s1 = getpos(pos1) ;
            int s2 = getpos(pos2) ;
            if(mp[s1] == 0) {
                mp[s1] = 1 ; cnt ++ ;
            }
            if(mp[s2] == 0) {
                mp[s2] = 1 ; cnt ++ ;
            }
            pos ++ ;
        }
        if (cnt >= N/2+1) {
            tmp[M++] = sa[i] ;
        }
        for(int j = i ; j < pos ; j ++ ) {
            int pos1 = sa[j] ;
            int pos2 = sa[j-1] ;
            int s1 = getpos(pos1) ;
            int s2 = getpos(pos2) ;
            mp[s1] = mp[s2] = 0 ;
        }
        i = pos ;
    }
    if(M > 0) return true ;
    return false ;
}

int main () {
    bool f = false;
    while(scanf("%d" , &N) != EOF) {
        if(N == 0) break ;
        if (f) printf("
") ;
        f = true ;
        int m = 26 ;
        n = 0 ;
        for (int i = 1 ; i <= N ; i ++ ) {
            scanf("%s" , S) ;
            int len = strlen(S) ;
            if (i != 1) s[n++] = ++m ;
            for(int j = 0 ; j < len ; j ++ ) {
                s[n++] = S[j] - 'a' + 1 ;
            }
            endpos[i] = n ;
        }

        s[n] = 0 ;
        da(s,sa,ra,he,n,m+1) ;
        int res = 0 ;
        int l = 0 , r = 1050 ;
        while(l <= r) {
            int mid = (l + r) / 2 ;
            if(check(mid)) {
                res = mid ;
                l = mid + 1 ;
                flushans() ;
            }
            else {
                r = mid - 1 ;
            }
        }
        if (res == 0) {
            printf("?
") ;
        }
        else {
            for(int i = 0 ; i < ansnum ; i ++ ) {
                int pos = ans[i] ;
                for(int i = 0 ; i < res ; i ++) {
                    printf("%c" , 'a'+s[pos+i]-1) ;
                }
                printf("
") ;
            }
        }
    }
}

SPOJ - PHRASES

求在所给n个串中存在至少两个不相交的最长子串

二分ans,分组后对每组check每个串的最左和最右出现地点

int getpos(int x) {
    for(int i = 1 ; i < N ; i ++ ) {
        if(x > endpos[i] && x <= endpos[i+1]) return i + 1 ;
    }
    return 1 ;
}

bool check(int mid) {
    int mp[15][2] ;
    for(int i = 1; i <= N ; i ++ ) {
        mp[i][0] = 999999999 ;
        mp[i][1] = -999999999 ;
    }

    for(int i = 1 ; i <= n ; i ++ ) {
        if(he[i] >= mid) {
            int s1 = getpos(sa[i]) ;
            int s2 = getpos(sa[i-1]) ;

            mp[s1][0] = min(mp[s1][0] , sa[i]) ;
            mp[s1][1] = max(mp[s1][1] , sa[i]) ;
            mp[s2][0] = min(mp[s2][0] , sa[i-1]) ;
            mp[s2][1] = max(mp[s2][1] , sa[i-1]) ;
        }
        else {
            bool ok = true ;
            for(int j = 1; j <= N ; j ++ ) {
                if(mp[j][1] - mp[j][0] < mid) ok = false ;
            }
            if(ok) return true;
            for(int j = 1; j <= N ; j ++ ) {
                mp[j][0] = 999999999 ;
                mp[j][1] = -999999999 ;
            }
        }
    }
    bool ok = true ;
    for(int i = 1; i <= N ; i ++ ) {
        if(mp[i][1] - mp[i][0] < mid) ok = false ;
    }
    if(ok) return true;
    return false ;
}

int main () {
    int t ; scanf("%d" , &t) ;
    while(t -- ) {
        scanf("%d" , &N) ;
        n = 0 ;
        m = 26 ;
        for(int i = 1 ; i <= N ; i ++ ) {
            if (i != 1) {
                s[n++] = ++m ;
            }
            scanf("%s" , S) ;
            int len = strlen(S) ;
            for(int j = 0 ; j < len ; j ++ ) {
                s[n++] = S[j] - 'a' + 1 ;
            }
            endpos[i] = n ;
        }
        s[n] = 0 ;

        da(s,sa,ra,he,n,m+100) ;

        int res = -1 ;
        int l = 0 , r = 10000 ;
        while( l <= r ) {
            int mid = (l + r) / 2 ;
            if(check(mid)) {
                res = mid ;
                l = mid + 1 ;
            }
            else {
                r = mid - 1 ;
            }
        }
        printf("%d
" , res) ;
    }
}

  

POJ1226

求多串的公共子串x的长度,要求是该串还有x 或 x的反向

将一个串变为它和它的反向串的连接,中间加连接符,认为它们还是原来那个串,二分ans后分组

int getpos(int x) {
    for(int i = 1 ; i < N ; i ++ ) {
        if(x > endpos[i] && x <= endpos[i+1]) return i + 1 ;
    }
    return 1 ;
}

bool check(int mid) {
    int mp[105] ;
    for(int i = 1; i <= N ; i ++ ) {
        mp[i] = 0 ;
    }

    for(int i = 1 ; i <= n ; i ++ ) {
        if(he[i] >= mid) {
            int s1 = getpos(sa[i]) ;
            int s2 = getpos(sa[i-1]) ;

            mp[s1] ++ ;
            mp[s2] ++ ;
        }
        else {
            bool ok = true ;
            for(int j = 1; j <= N ; j ++ ) {
                if (mp[j] <= 0) ok = false ;
            }
            if(ok) return true;
            for(int j = 1; j <= N ; j ++ ) {
                mp[j] = 0 ;
            }
        }
    }
    bool ok = true ;
    for(int i = 1; i <= N ; i ++ ) {
        if(mp[i] <= 0) ok = false ;
    }
    if(ok) return true;
    return false ;
}

int main () {
    int t ; scanf("%d" , &t) ;
    while(t -- ) {
        scanf("%d" , &N) ;
        n = 0 ;
        m = 256 ;
        for(int i = 1 ; i <= N ; i ++ ) {
            if (i != 1) {
                s[n++] = ++m ;
            }
            scanf("%s" , S) ;
            int len = strlen(S) ;
            for(int j = 0 ; j < len ; j ++ ) {
                s[n++] = S[j] ;
            }
            s[n++] = ++m ;
            for(int j = len-1 ; j >= 0 ; j -- ) {
                s[n++] = S[j] ;
            }
            endpos[i] = n ;
        }
        s[n] = 0 ;

        da(s,sa,ra,he,n,m+100) ;

        int res = -1 ;
        int l = 0 , r = 100 ;
        while( l <= r ) {
            int mid = (l + r) / 2 ;
            if(check(mid)) {
                res = mid ;
                l = mid + 1 ;
            }
            else {
                r = mid - 1 ;
            }
        }
        printf("%d
" , res) ;
    }
}

  

UVA - 11475

在所给串后添加尽量少的字母使其成为回文串

题目可以转化为求所给串的后缀回文串的最长长度,将所给串的反串拼接在后面,就可以使用lcp枚举回文串中心判断了

int trans(char a) {
    if (a <= 'z' && a >= 'a') return (a - 'a' + 1) ;
    else return (a - 'A' + 1 + 26) ;
}

int main () {
    while(scanf("%s" , S) != EOF) {
        int len = strlen(S) ;
        for(int i = 0 ; i < len ; i ++ ) s[i] = trans(S[i]) ;
        n = len ;
        s[n++] = 60 ;
        for(int i = len - 1; i >= 0 ; i -- ) s[n++] = trans(S[i]) ;
        s[n] = 0 ;
        da(s,sa,ra,he,n,100) ;
        init_rmq(n) ;
        int maxlen = 0 ;
        for(int i = 1 ; i <= len ; i ++ ) {
            int lc = lcp(len - i, len + i) ;
            if (len - i + lc - 1 >= len - 1) maxlen = max((lc * 2 - 1), maxlen) ;
        }
        for(int i = 1 ; i <= len - 1 ; i ++ ) {
            int lc = lcp(len - i , len + i + 1) ;
            if (len - i + lc - 1 >= len - 1) maxlen = max(lc*2 , maxlen) ;
        }
        printf("%s" , S) ;
        for(int i = len - maxlen - 1 ; i >= 0 ; i -- ) printf("%c" , S[i]) ;
        printf("
") ;
    }
}

  

POJ3581

给出一个串,将其分作三段,分别反置,使之后的串字典序最小,a[1] > a[other]

第一段肯定是反置整个串,排序后缀后取最小的那个,且位置得给后两个串机会,因为a[1]是极大的,所以不影响后面

那么问题就变成了"给出一个串,分作两段,分别反置,使之后的串字典序最小",如果还按照之前的方法做可能会出问题

所给串若是s1s2...sksk+1....sn-1sn,那么我们选择第一段s1~sk,反转后就是sk~s1 sn~sk+1,它的内在和k无关,是sn~s1 sn~s1的子串!

于是问题就变成了,求一个len = n*2的串中长度为n的最小子串了,我们排序一下,取位置为1~n-1为开头的后缀,这个后缀的长度虽然有可能>n,但是我们保证它的前n位一定是可选的后缀的前n位中最大的,因为即使算n+1位我们也不会输

vector<int>b ;

int main () {
    b.clear() ;
    scanf("%d" , &n) ;
    for(int i = 0 ; i < n ; i ++ ) {
        scanf("%d" , &s[i]) ;
        b.push_back(s[i]) ;
    }
    sort(b.begin(), b.end()) ;
    b.erase(unique(b.begin(),b.end()),b.end()) ;
    for(int i = 0 ; i < n ; i ++ ) s[i] = lower_bound(b.begin(),b.end(),s[i]) - b.begin() + 1 ;  /// 1 ~ n
    /*
    int maxx = s[0] , pos = 0 ;
    for(int i = 1 ; i < n - 2; i ++ ) {
        if(s[i] <= maxx) {
            maxx = s[i] ;
            pos = i ;
        }
    }
    */
    for(int i = 0 ; i < n / 2 ; i ++ ) swap(s[i] , s[n-i-1]) ;
    s[n] = 0 ;
    da(s,sa,ra,he,n,200050) ;
    int pos ;
    for(int i = 1 ; i <= n ; i ++ ) {
        int po = sa[i] ;
        if(po > 1) {
            pos = po ; break ;
        }
    }
    for(int i = pos ; i < n ; i ++ ) printf("%d
" , b[s[i]-1]) ;
    pos = n - pos - 1 ;
    for(int i = 0 ; i < n / 2 ; i ++ ) swap(s[i] , s[n-i-1]) ;

    int nn = n - pos - 1;
    for(int i = 0 ; i < nn ; i ++ ) {
        s[i] = s[pos + i + 1] ;
    }
    n = nn ;
    for(int i = 0 ; i < n / 2 ; i ++ ) swap(s[i] , s[n-i-1]) ;
    for(int i = 0 ; i < n ; i ++ ) s[n + i] = s[i] ;
    n += n ;
    s[n] = 0 ;

    da(s,sa,ra,he,n,200050) ;

    int ans = 0 ;
    for(int i = 1 ; i <= n ; i ++ ) {
        int pos = sa[i] ;
        if(pos >= 1 && pos < nn) {
            ans = pos ; break ;
        }
    }
    for(int i = 0 ; i < nn ; i ++ ) printf("%d
" , b[s[ans+i]-1]) ;
}

  
SPOJ REPEATS

求所给串中的一个子串,能最多被多少个小串重复而来

很精妙的思想,由三点组成

1 如果枚举长度len,且我认为可能有一个子串被三个小串重复而来,那么s[0] s[len] s[len*2]...它一定要覆盖三个点,这是长度必须的

如果我们认为是两个小串,就可以对s[x] 和 s[pos]求lcp,如果lcp能够覆盖住s[pos] ~ s[pos + len - 1],那么毫无疑问是可以的,但是三个呢?我们不能这样暴力去求

2 然而,如果对s[x]和s[pos]求lcp,lcp很长,说明后面有好多个重复小串,因为一旦s[x]和s[pos]是不同的字符,那么后面肯定也是扭曲的很有规律的串,所以对两个点求lcp就可以求出,假设这两个点是我覆盖的三个点中的前两个,如果lcp能够覆盖到s[pos] ~ s[pos + len * 2 - 1] , 那么我求s[pos]和s[pos+len]的lcp,结果也是一样的

3 如果答案是aabaab,而此时我的x=1,pos=4,这时候lcp只能覆盖到5,我就会误算,认为只能由一个小串构成,怎么办?

我如果误算了,就会少算一个,绝对不会少算多个,对于每次我枚举覆盖的第一个点和第二个点,我都尝试根据所得lcp进行“修正”,尝试让结果+1,如果当前所求的lcp还差一位就能/len的结果更大,使ans+1,那我就让x和pos往前移动一位,看看是否真的增大了结果。

int main () {
    int t ; scanf("%d" , &t) ;
    while(t -- ) {
        int n ; scanf("%d" , &n) ;
        for(int i = 0 ; i < n ; i ++ ) {
            char SS[2]; scanf("%s" , SS) ;
            s[i] = SS[0] - 'a' + 1 ;
        }
        s[n] = 0 ;
        da(s,sa,ra,he,n,10) ;
        init_rmq(n) ;

        int ans = 0 ;
        for(int len = 1 ; len <= n ; len ++ ) {
            for(int pos = 0 ; pos + len < n ; pos += len) {
                int qlen = lcp(pos,pos+len) ;
                ans = max(ans , qlen / len + 1) ;

                int need = len - qlen % len ; need %= len ;
                if(pos - need >= 0) {
                    qlen = lcp(pos - need, pos-need+len) ;
                    ans = max(ans , qlen / len + 1) ;
                }
            }
        }
        printf("%d
" , ans) ;
    }
}

  

 POJ 3693

上题的升级版,需要输出那个子串

我们可以在更新ans的时候记录,不过当ans == qlen / len + 1时我们无法确定谁更强,虽然我们排过序了,但是dcdcb的时候我们会认为dcb比较优秀,因为我们排序的时候未曾去了解过长度,但是在更新那个最小子串的时候是有长度这个限制的

我们会发现当ab两个串长度不同,重复次数相同,且短串是长串的子串的时候才会发现错误,于是我们枚举rank,然后把所有可行解从小到大check一遍就行了

vector<int>b ;

int main () {
    int t ; scanf("%d" , &t) ;
    int cas = 1 ;
    while(scanf("%s" , S) != EOF) {
        n = strlen(S) ;
        if(n == 1 && S[0] == '#') break ;
        for(int i = 0 ; i < n ; i ++ ) s[i] = S[i] - 'a' + 1 ;
        s[n] = 0 ;
        da(s,sa,ra,he,n,100) ;
        init_rmq(n) ;
        int ans = 0 ;
        int le = 0;
        for(int len = 1 ; len <= n ; len ++ ) {
            for(int pos = 0 ; pos + len < n ; pos += len) {
                int qlen = lcp(pos,pos+len);
                if(qlen / len + 1 > ans) {
                    ans = qlen / len + 1 ;
                    le = len ;
                    b.clear() ;
                    b.push_back(pos) ;
                }
                else if (qlen / len + 1 == ans) {
                    b.push_back(pos) ;
                }
                int need = len - qlen % len ; need %= len ;
                if(pos - need >= 0) {
                    qlen = lcp(pos - need , pos - need + len) ;
                    if (qlen / len + 1 > ans) {
                        ans = qlen / len + 1 ;
                        le = len ;
                        b.clear() ;
                        b.push_back(pos-need) ;
                    }
                    else if (qlen / len + 1 == ans) {
                        b.push_back(pos-need) ;
                    }
                }
            }
        }
        printf("Case %d: ", cas ++ ) ;
        if (ans == 0) {
            printf("%c" , s[sa[1]]) ;
        }
        else {
            bool fin = false ;
            for (int i = 1 ; i <= n ; i ++ ) {
                if(fin) break ;
                for(int j = 0 ; j < b.size(); j ++ ) {
                    int pos = sa[i] ;
                    if (pos + le < n) {
                        int lc = lcp(pos,pos+le) ;
                        if ((lc+le)/le >= ans) {
                            fin=true;
                            for(int k = 0 ; k < ans * le ; k ++) {
                                printf("%c" , 'a' + s[k + pos] - 1) ;
                            }
                            printf("
") ;
                            break;
                        }
                    }
                }
            }
        }
    }
}

  

POJ - 3415

求AB两串中长度>=k的相同子串对的数目

如果我们sort完,按照he>=k进行分类之后,从左往右,每扫到一个A的串都可以向前暴力寻找B的串计数,但是这样就太暴力了

对同一组,我们会意识到,如果有b1 b2 a三个串,a和b2的lcp >= a和b1的lcp,这个满足单调性,于是我们可以对同一组算两次,第一次算A,将所有的B的height和数量(1)加入栈,算一次a,这时候的贡献是栈中的he总和,之后我们再压入B的时候,如果这个新的height比栈顶的小,说明这时候A的子串不能再和栈中的串有那么大的lcp,于是我们将栈中的串的height对sum的贡献更改为新的height,这就是单调栈的用法,计算B的时候同理。

L getpos(L x) {
    if(x <= pren) return 0 ;
    else return 1 ;
}

stack<int>a ;
stack<int>b ;

int main () {
    while(scanf("%lld" , &k) != EOF) {
        if( k == 0 ) break ;
        scanf("%s" , S) ;
        L len = strlen(S) ;
        n = 0 ;
        for(L i = 0 ; i < len ; i ++ ) s[n ++ ] = S[i] ;
        s[n ++ ] = 270 ;
        pren = n - 1;
        scanf("%s" , S) ;
        len = strlen(S) ;
        for(L i = 0 ; i < len ; i ++ ) s[n ++ ] = S[i] ;
        s[n] = 0 ;
        da(s,sa,ra,he,n,300);

        L ans = 0 ;
        L sum = 0 ;
        for(L i = 1 ; i <= n ; i ++ ) {
            L pos = i ;
            while(pos <= n && he[pos] >= k) pos ++ ;
            /// A
            while(!a.empty())a.pop();
            while(!b.empty())b.pop();
            sum = 0 ;
            for(L j = i ; j < pos ; j ++ ) {
                L pos1 = sa[j-1] ;
                L num = 0 ;
                if(getpos(pos1) == 1) { /// last = B
                    num ++ ;
                    sum += he[j] - k + 1 ;
                }
                while(!a.empty()) {
                    L he1 = a.top() , num1 = b.top() ;
                    if(he[j] <= he1) {
                        a.pop() ;
                        b.pop() ;
                        num += num1 ;
                        sum -= (he1 - he[j]) * num1 ;
                    }
                    else {
                        break ;
                    }
                }
                a.push(he[j]) ;
                b.push(num) ;
                L pos2 = sa[j] ;

                if(getpos(pos2) == 0) { /// now calc A
                    ans += sum ;
                }
            }
            /// B
            while(!a.empty())a.pop();
            while(!b.empty())b.pop();
            sum = 0 ;
            for(L j = i ; j < pos ; j ++ ) {
                L pos1 = sa[j-1] ;
                L num = 0 ;
                if(getpos(pos1) == 0) { /// last = A
                    num ++ ;
                    sum += he[j] - k + 1 ;
                }
                while(!a.empty()) {
                    L he1 = a.top() , num1 = b.top() ;
                    if(he[j] <= he1) {
                        a.pop() ;
                        b.pop() ;
                        num += num1 ;
                        sum -= (he1 - he[j]) * num1 ;
                    }
                    else {
                        break ;
                    }
                }
                a.push(he[j]) ;
                b.push(num) ;
                L pos2 = sa[j] ;

                if(getpos(pos2) == 1) { /// now calc B
                    ans += sum ;
                }
            }
            i = pos ;
        }
        printf("%lld
" , ans) ;
    }
}

kuangbin专题基本做完了,接下来做一下近两年的多校&online&onsite的题目

 Gym - 101194F

16ec的题目,给n个串,找出第一个串的最短子串使其不在其余串中出现

陷入了一个坑,之前都是最长,于是可以二分ans然后分组check,现在二分完发现没有办法很好的check

思考几个性质:1 假如后缀A和后缀B的lcp为3,那么后缀A的前3项一定在B中出现过了,前4项则未必

2 假如存在后缀ABC,她们的rank依次,则lcp(b,c) >= lcp(a,c) 

于是我们排序后,就可以对所有的第一个串的后缀check,找到她左边第一个非第一个串的后缀,求lcp为x,那么它的前x项一定不是答案。然后再寻找右边的第一个非第一个串的后缀,求lcp为y,那么就可以得出答案,该后缀的前max(x,y)+1项是答案,不过需要判断这些项是否超出了第一个串的size

通过这道题感觉到,板子只是一个板子,我们可以对后缀进行排序而已,剩下的东西都没有定式,我们只是得到了一些有用的东西,需要做的还是在这些东西上蹦蹦跳跳

int lef[maxn] , rig[maxn] ;
void ycl() {
    int last = -1 ;
    for(int i = 1 ; i <= n ; i ++ ) {
        lef[i] = last ;
        if(getpos(sa[i]) == 2) last = i ;
    }
    last = -1 ;
    for(int i = n ; i >= 1 ; i -- ) {
        rig[i] = last ;
        if(getpos(sa[i]) == 2) last = i ;
    }
}

int main () {
    int t ; scanf("%d" , &t) ;
    int cas = 1 ;
    while(t -- ) {
        scanf("%d" , &N) ;
        n = 0 ;
        m = 30 ;
        for(int i = 1 ; i <= N ; i ++ ) {
            scanf("%s" , S) ;
            int len = strlen(S) ;
            if (i != 1) s[n ++ ] = ++ m ;
            for(int j = 0 ; j < len ; j ++) s[n ++ ] = (S[j] - 'a' + 1) ;
            endpos[i] = n - 1;
        }
        s[n] = 0 ;
        da(s,sa,ra,he,n,50000 + 50) ;
        ycl();
        init_rmq(n);

        int len = 999999999 , ans = 0 ;
        for(int i = 1 ; i <= n ; i ++ ) {
            if(getpos(sa[i]) == 1) {
                int can = endpos[1] - sa[i] + 1 ;
                int pos1 = sa[i] ;
                int pos2 ;
                int lc = 0 ;
                if(lef[i] != -1) {
                    pos2 = sa[lef[i]] ;
                    lc = max(lcp(pos2,pos1),lc);
                }
                if(rig[i] != -1) {
                    pos2 = sa[rig[i]];
                    lc = max(lcp(pos2,pos1),lc);
                }
                if(can > lc) {
                    can = lc + 1 ;
                    if(can < len) {
                        len = can ;
                        ans = pos1;
                    }
                    else if(can == len){
                        if(ra[ans] > ra[pos1]) {
                            ans = pos1 ;
                        }
                    }
                }

            }
        }

        printf("Case #%d: " , cas ++ ) ;
        if(len == 999999999) {
            printf("Impossible
") ;
            continue ;
        }
        for(int i = 0 ; i < len ; i ++ ) printf("%c" , 'a' + s[ans + i] - 1) ;
        printf("
") ;
    }
}

HDU - 6194

17Shenyang online 求一个串中严格出现k次的子串

所有的子串都是后缀的前缀,于是对于rank = i 来说,rank=i-k+1的前缀的lcp可能为ans做贡献

但是及时是i和i-k+1的lcp,也可能不是ans,因为要求严格出现k次,这个lcp可能出现>k次,如何限制?

我统计第i个串作为那些子串的最大rank的父串,假设第i-k+1个串是最小rank的父串,那么i-k和i+1号串不应当含有这些子串,于是用lcp乱统计一下就可以了

需要注意的是,因为lcp(a,a)会出问题导致rmq的计算RE,于是需要特判

int main () {
    L t ; scanf("%lld" , &t) ;
    while(t -- ) {
        scanf("%lld" , &k) ;
        scanf("%s" , S) ;
        L len = strlen(S) ;
        n = 0 ;
        for(L i = 0 ; i < len ; i ++ ) s[n ++ ] = S[i] ;
        s[n] = 0 ;
        da(s,sa,ra,he,n,500) ;
        init_rmq(n) ;
        L ans = 0 ;
        for(L i = k ; i <= n ; i ++ ) {
            L pos1 = sa[i] ;
            L pre = i - k + 1 ;
            L lastpos = sa[pre] ;

            L can ;
            if(lastpos != pos1) {
                can = lcp(lastpos,pos1) ;
            }
            else {
                can = n - sa[i] ;
            }

            L l = 0 ;
            L left = pre - 1 ;
            if (left >= 1) {
                L pos2 = sa[left] ;
                L lc = lcp(pos1,pos2) ;
                l = max(lc,l) ;
            }
            if(i + 1 <= n) {
                L pos2 = sa[i+1] ;
                L lc = lcp(pos1,pos2) ;
                l = max(lc,l) ;
            }

            if(can > l) {
                ans += can - l ;
            }
        }
        printf("%lld
" , ans) ;
    }
}

  

比赛结束了。。很难接受这个结果。。

可是以后的路还很长。。

在未曾涉足的领域要比对待算法更加努力的去学习啊!

原文地址:https://www.cnblogs.com/rayrayrainrain/p/9877291.html