GDKOI 2014 基因模式 基于SAM的算法

题目大意

给一个只有4种字符的原串 T ,想在有 M 条询问,每条询问给你一个也只包含这四种字符的字符串和某些字符出现奇偶性的限制。现在要求找出这个字符串有多少子串既为原串的子串,也满足限制。

T 的长度 100000 M 个字符串的字符总数 100000 M100

解题思路

满足第一个条件,即为原串的子串比较好实现,直接建一个原串的 SAM 在把要处理的串在上面跑,记录每个位置往前最远能到哪里就可以了(记为 Neari ),那么我们应该怎么奇偶性限制的问题呢?我们用一个四位的二进制数,表达出所有字符奇偶性的情况,可以用异或来实奇偶的转换(每种情况即对应 0 ~ 15 )。
然后我们就可以记录所有前缀的情况,即用 Ref[i] 表示前缀 i 这个字符串表示的情况,用 Num[i][j] 表示位置 i 前面所有前缀情况为 j 的数量(即Num[i] = Num[i - 1], Num[i][Ref[i]] ++)。有个很优美的性质就是,我们拿 Refr 异或 Refl1 就可以得到区间 l r 的奇偶情况,每次当我们做到第i个位置时,我们已经知道了前面每种状态出现的次数,我们把一个状态看作一个整体,判断是否合法,如果合法,答案就可以有前缀和 Num 来累加上 Neari ~ i 中这个情况的个数就可以了。具体方法可以看程序操作,程序中体现的很直观。

程序

//GDKOI 2014 YxuanwKeith
#include 
#include 
#include 
#include 

using namespace std;

const int MAXN = 1e5 + 5, MAXL = 1 << 4;

struct SAM {int Pre, Len, Go[4];} A[MAXN * 2];

map<char,int> D;
int S, Q, tot, Last, Str[MAXN], L[MAXN], Lim[4], Num[MAXN][MAXL], Ref[MAXN];

void Read(int *S) {
    char c;
    while (c = getchar(), c <= ' ');
    S[S[0] = 1] = D[c];
    while (c = getchar(), c > ' ') S[++ S[0]] = D[c];
}

void Add(int c) {
    int Np = ++ tot, p = Last;
    A[Np].Len = A[p].Len + 1, Last = Np;
    for (; p && !A[p].Go[c]; p = A[p].Pre) A[p].Go[c] = Np;
    if (!p) A[Np].Pre = S; else {
        int q = A[p].Go[c];
        if (A[p].Len + 1 == A[q].Len) A[Np].Pre = q; else {
            int Nq = ++ tot;
            A[Nq] = A[q];
            A[q].Pre = A[Np].Pre = Nq;
            A[Nq].Len = A[p].Len + 1;
            for (; p && A[p].Go[c] == q; p = A[p].Pre) A[p].Go[c] = Nq;
        }
    }
}

void GetNum() {
    Num[0][0] = 1;
    for (int i = 1; i <= Str[0]; i ++) {
        Ref[i] = Ref[i - 1] ^ (1 << Str[i]);
        for (int j = 0; j < MAXL; j ++) Num[i][j] = Num[i - 1][j];
        Num[i][Ref[i]] ++;
    }
}

int main() {
    D['A'] = 0, D['T'] = 1, D['G'] = 2, D['C'] = 3;
    D['a'] = 4, D['t'] = 5, D['g'] = 6, D['c'] = 7;
    scanf("%d", &Q);
    Read(Str);
    Last = tot = S = 1;
    for (int i = 1; i <= Str[0]; i ++) Add(Str[i]);
    for (int i = 1; i <= Q; i ++) {
        Read(Str), Read(L);
        GetNum();
        int Q = MAXL - 1, O = MAXL - 1;
        for (int j = 1; j <= L[0]; j ++)
            if (L[j] < 4) O ^= 1 << L[j]; else Q ^= 1 << (L[j] - 4);
        long long Ans = 0;
        int p = S, Len = 0;
        for (int i = 1; i <= Str[0]; i ++) {
            if (A[p].Go[Str[i]]) Len ++, p = A[p].Go[Str[i]]; else {
                for (; p && !A[p].Go[Str[i]]; p = A[p].Pre);
                if (p) Len = A[p].Len + 1, p = A[p].Go[Str[i]]; else
                    Len = 0, p = S;
            }
            for (int j = 0; j < MAXL; j ++) {
                int t = j ^ Ref[i], opp = (~t);
                if (((t & Q) == t) && ((opp & O) == (opp & (MAXL - 1)))) {
                    Ans += Num[i - 1][j];
                    if (i != Len) Ans -= Num[i - Len - 1][j];
                }
            }
        }
        printf("%lld\n", Ans);
    }
} 

你可能感兴趣的:(算法-String,算法-后缀自动机)