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 <script type="math/tex" id="MathJax-Element-17">i</script>中这个情况的个数就可以了。具体方法可以看程序操作,程序中体现的很直观。

程序

//GDKOI 2014 YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>

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);
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值