• 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) ;
        }
    }
    

      

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

    可是以后的路还很长。。

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

  • 相关阅读:
    Runtime源码解析(JDK1.8)
    剑指Offer-孩子们的游戏(圆圈中最后剩下的数)
    剑指Offer-把二叉树打印成多行
    剑指Offer-按之字形顺序打印二叉树
    剑指Offer-对称的二叉树
    剑指Offer-删除链表中重复的结点
    剑指Offer-链表中环的入口结点
    剑指Offer-字符流中第一个不重复的字符
    剑指Offer-构建乘积数组
    selenium获取微博用户粉丝数
  • 原文地址:https://www.cnblogs.com/rayrayrainrain/p/9877291.html
Copyright © 2020-2023  润新知