【SPOJ 8093】Sevenk Love Oimaster

http://www.spoj.com/problems/JZPGYZ/
查询一个询问串在上面n个串中多少个串的子串。
后缀数组+主席树,常熟有点大。。。
建出广义SAM,利用parent树的dfs序,每次相当于询问parent树的一个子树中出现了多少不同的颜色。
可以用主席树统计,pre表示dfs序上与这个位置属于同一个串的前一个位置在哪,主席树询问区间内的pre小于这个区间左端点的个数(也就是不同的颜色数)。
或者更方便地,把每个询问拆成parent树dfs序上的两个前缀和相减。
离线排序前缀和从左到右用bits维护pre为权值的树状数组并且查询小于某个区间左端点的pre的个数,统计答案时做一下减法就可以了(类似主席树)。
时间复杂度\(O(n\log n)\)
还有不科学的暴力的做法,对于n个模板串,暴力在parent树上打标记,时间复杂度\(O(n^2)\)
数据并没有卡这种做法。于是我本着复习广义后缀自动机的原则(和懒得写bits的原则),只写了这种暴力的做法。。。
将近花了一天重新脑补了一下广义SAM,感觉以前直接套用普通SAM的插入模板导致多出来了一些点不美观(当然多出来的这些点都可以合并到它们的parent),就重写了一下不会多出来点的广义SAM模板,常数更大,代码更长qwq,但保证所有节点的Right集合都是它们parent的Right集合的真子集(及不用费心想着哪些点可以合并在一起了)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 360003;

char s[N];
int l[N], r[N];

struct State {
    State *par, *go[50];
    int val, tim, cnt;
    State(int _num) : val(_num) {
        par = 0; tim = cnt = 0;
        memset(go, 0, sizeof(go));
    }
} *root, *last;

void extend(int w) {
    if (last->go[w] && last->go[w]->val == last->val + 1) {last = last->go[w]; return;}
    State *p = last;
    State *np = new State(p->val + 1);
    while (p && p->go[w] == 0)
        p->go[w] = np, p = p->par;
    if (p == 0) np->par = root;
    else {
        State *q = p->go[w];
        if (q->val == p->val + 1) np->par = q;
        else if (p != last) {
                State *nq = new State(p->val + 1);
                memcpy(nq->go, q->go, sizeof(q->go));
                nq->par = q->par; q->par = np->par = nq;
                while (p && p->go[w] == q)
                    p->go[w] = nq, p = p->par;
            } else {
                memcpy(np->go, q->go, sizeof(q->go));
                np->par = q->par; q->par = np;
                while (p && p->go[w] == q)
                    p->go[w] = np, p = p->par;
            }
    }
    last = np;
}

void mark(State *t, int timing) {
    if (t == root || t->tim == timing) return;
    t->tim = timing; ++t->cnt;
    mark(t->par, timing);
}

int n, m;

int main() {
    root = new State(0);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        l[i] = r[i - 1] + 1;
        scanf("%s", s + l[i]);
        r[i] = l[i] + strlen(s + l[i]) - 1;
    }
    
    for (int i = 1; i <= n; ++i) {
        last = root;
        for (int j = l[i], top = r[i]; j <= top; ++j)
            extend(s[j] - 'a');
    }
    
    State *tmp;
    for (int i = 1; i <= n; ++i) {
        tmp = root;
        for (int j = l[i], top = r[i]; j <= top; ++j) {
            tmp = tmp->go[s[j] - 'a'];
            mark(tmp, i);
        }
    }
    
    int len; bool flag;
    for (int i = 1; i <= m; ++i) {
        scanf("%s", s);
        len = strlen(s);
        tmp = root; flag = true;
        for (int j = 0; j < len; ++j)
            if (tmp->go[s[j] - 'a'])
                tmp = tmp->go[s[j] - 'a'];
            else {
                flag = false;
                break;
            }
        if (!flag) puts("0");
        else printf("%d\n", tmp->cnt);
    }
    return 0;
}

转载于:https://www.cnblogs.com/abclzr/p/6653471.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值