BZOJ 3473 字符串

广义后缀树+set

子串问题就考虑后缀数据结构。考虑一棵后缀树,一个树上的串合法,当且仅当这个串在至少k个串的后缀树链的并上。那就用SAM做出后缀树,用set维护一个点的链并,即哪些串经过了这个点。然后枚举每一个串跑SAM,对于一个位置i,暴力跳fail直到链并不小于k为止。

广义SAM和单串SAM的差别就是刚开始extend的时候要判断是否有一个状态已经包含了当前要增加的状态并且判断这个状态的len和当前要增加的状态的len的关系。

听说set的合并要用a.swap(b),这样复杂度是O(1)的。直接swap我也不知道复杂度如何。

#include<set>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define A 28
#define H 20
using namespace std;
namespace runzhe2000
{
    typedef long long ll;
    vector<int> node[N];
    char s[N], *str[N];
    int ecnt, n, k, last[N<<1], good[N<<1];
    struct edge{int next, to;}e[N<<1];
    void addedge(int a, int b)
    {
        e[++ecnt] = (edge){last[a], b};
        last[a] = ecnt;
    }

    struct SAM
    {
        SAM *fail, *next[A];
        int len;
        set<int> S;
    }mem[N<<1], *tot, *null, *root;
    SAM* newSAM(int v = 0)
    {
        *++tot = *null; 
        if(v)tot->S.insert(v), node[v].push_back(tot - mem);
        return tot;
    }
    void init()
    {
        null = tot = mem; null->fail = null; null->len = 0;
        for(int i = 0; i < A; i++) null->next[i] = null;
        root = newSAM();
    }
    SAM* extend(SAM *p, int v, int id)
    {
        if(p->next[v] != null)
        {
            SAM *q = p->next[v];
            if(p->len + 1 == q->len) {q->S.insert(id); return q;}
            else
            {
                SAM *nq = newSAM(id);
                nq->fail = q->fail; nq->len = p->len + 1; 
                for(int i = 0; i < A; i++) nq->next[i] = q->next[i];
                q->fail = nq;
                for(; p->next[v] == q && p != null; p = p->fail) p->next[v] = nq;
                return nq;
            }
        }
        else
        {
            SAM *np = newSAM(id); np->len = p->len + 1;
            for(; p->next[v] == null && p != null; p = p->fail) p->next[v] = np;
            if(p == null) np->fail = root;
            else
            {
                SAM *q = p->next[v];
                if(p->len + 1 == q->len) np->fail = q;
                else
                {
                    SAM *nq = newSAM(id);
                    nq->fail = q->fail; nq->len = p->len + 1;
                    for(int i = 0; i < A; i++) nq->next[i] = q->next[i];
                    np->fail = q->fail = nq;
                    for(; p->next[v] == q && p != null; p = p->fail) p->next[v] = nq;
                }
            }
            return np;
        }
    }
    void build_tree()
    {
        for(SAM *i = tot; i != mem; i--)
            addedge(i->fail-mem, i-mem);
    }
    void merge_set(set<int> &a, set<int> &b)
    {
        if(a.size() < b.size()) a.swap(b); // 这样换set是O(1)的 
        for(; !b.empty(); ) a.insert(*b.begin()), b.erase(b.begin());
    }
    void dfs(int x)
    {
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            dfs(y); merge_set(mem[x].S, mem[y].S);
        }
        if((int)mem[x].S.size() >= k) good[x] = 1;
    }
    void main()
    {
        scanf("%d%d",&n,&k); 
        if(n < k) {for(int i = 1; i <= n; i++)printf("0 "); return;}
        init(); 
        for(int i = 1; i <= n; i++)
        {
            scanf("%s",s); 
            str[i] = new char[strlen(s) + 5]();
            memcpy(str[i], s, sizeof(char)*(strlen(s)+5));
            SAM *last = root;
            for(int j = 0, jj = strlen(s); j < jj; j++)
                last = extend(last, s[j] - 'a', i);
        }
        build_tree();
        dfs(root - mem);
        for(int i = 1; i <= n; i++)
        {
            SAM *p = root; ll ans = 0;
            for(int j = 0, jj = strlen(str[i]); j < jj; j++)
            {
                p = p->next[str[i][j] - 'a'];
                for(; !good[p-mem]; p = p->fail);
                ans += p->len;
            }
            printf("%lld ",ans);
        }
    }
}
int main()
{
    runzhe2000::main();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值