zoukankan      html  css  js  c++  java
  • uva11107(后缀数组)

    uva11107

    题意

    输入 n 个 DNA 序列,求出长度最大的字符串,使得它在超过一半的 DNA 序列中连续出现。如果有多解,按字典序输出。

    分析

    论文

    后缀数组经典题。加深几个关键数组的印象。

    和 poj2774 一样,都是要去连接字符串,保证分隔符不能和字符串内的字符相同,且不能重复。
    为什么要连接呢?因为求后缀数组实际是对后缀字符串进行排序,那么有公共前缀子串的后缀字符串会尽可能的排在一起,不同的分隔符保证公共子串不会扩散到别的串上。而 height 数组对应的就是相邻 sa 数组的 lcp ( 最长公共前缀 )。根据选择的最大长度 m,可以将连续的且 lcp 长度大于等于 m 的后缀子串分到一组,要去掉那些在同一个原串里的子串,用一个标记数组标记当前字符属于哪个原串。最后统计个数是否大于一半即可。

    这种求最大、最小应该想到和二分法有关。

    code

    #include<cstdio>
    #include<cstring>
    #include<set>
    #include<algorithm>
    using namespace std;
    const int MAXN = 2e5 + 10;
    char s[MAXN];
    int sa[MAXN], t[MAXN], t2[MAXN], c[MAXN], n; // n 为 字符串长度 + 1,s[n - 1] = 0
    
    int rnk[MAXN], height[MAXN];
    // 构造字符串 s 的后缀数组。每个字符值必须为 0 ~ m-1
    void build_sa(int m) {
        int i, *x = t, *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(int k = 1; k <= n; k <<= 1) {
            int p = 0;
            for(i = n - k; i < n; i++) y[p++] = i;
            for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;
            for(i = 0; i < m; i++) c[i] = 0;
            for(i = 0; i < n; i++) c[x[y[i]]]++;
            for(i = 0; 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]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
            if(p >= n) break;
            m = p;
        }
    }
    void getHeight() {
        int i, j, k = 0;
        for(i = 0; i < n; i++) rnk[sa[i]] = i;
        for(i = 0; i < n - 1; i++) {
            if(k) k--;
            j = sa[rnk[i] - 1];
            while(s[i + k] == s[j + k]) {
                k++;
            }
            height[rnk[i]] = k;
        }
    }
    // 保证 s[n-1] = 0 且前面非 0 // 也就是说空串在最前
    // sa[0] = n - 1,sa[i] 有效的只有 [1, n-1] ( 因为前面的 n 加 1 了 )表示第 i 位的是谁( 以第几个字符开始的字符串后缀 )
    // height[i] 有效的只有 [2, n-1] 表示 lcp(sa[i], sa[i-1]) 最长公共前缀
    char s1[MAXN];
    int id[MAXN];
    int check(int c, int m) {
        set<int> S;
        S.insert(id[sa[1]]);
        for(int i = 2; i < n; i++) {
            while(i < n && height[i] >= m) {
                S.insert(id[sa[i]]);
                i++;
            }
            if(2 * S.size() > c) return 1;
            S.clear();
            S.insert(id[sa[i]]);
        }
        return 0;
    }
    void print(int c, int m) {
        set<int> S;
        S.insert(id[sa[1]]);
        for(int i = 2; i < n; i++) {
            while(i < n && height[i] >= m) {
                S.insert(id[sa[i]]);
                i++;
            }
            if(2 * S.size() > c) {
                int bgn = sa[i - 1];
                for(int j = 0; j < m; j++) {
                    printf("%c", s[bgn + j]);
                }
                puts("");
            }
            S.clear();
            S.insert(id[sa[i]]);
        }
    }
    int main() {
        int c;
        int f = 1;
        while(scanf("%d", &c) && c) {
            memset(s, 0, sizeof s);
            if(!f) puts("");
            else f = 0;
            int bound = 1;
            for(int i = 0; i < c; i++) {
                scanf("%s", s1);
                int l = strlen(s), l1 = strlen(s1);
                for(int j = 0; j < l1; j++) {
                    s[j + l] = s1[j];
                    id[j + l] = i;
                }
                if(bound == 97) bound = 123;
                s[l + l1] = bound++; // 分隔符
                id[l + l1] = i;
                s[l + l1 + 1] = 0;
            }
            if(c == 1) {
                puts(s1); continue;
            }
            n = strlen(s) + 1; // 保证 s[n-1] = 0
            build_sa(128);
            getHeight();
            int l = 0, r = 1000, mid, ans = 0;
            while(l <= r) {
                mid = (l + r) / 2;
                if(check(c, mid)) { ans = mid; l = mid + 1; }
                else r = mid - 1;
            }
            if(ans == 0) puts("?");
            else print(c, ans);
        }
        return 0;
    }
    
  • 相关阅读:
    笔记-归并排序
    Repeated Substring Pattern
    Assign Cookies
    Number of Boomerangs
    Paint Fence
    Path Sum III
    Valid Word Square
    Sum of Two Integers
    Find All Numbers Disappeared in an Array
    First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/ftae/p/7192174.html
Copyright © 2011-2022 走看看