[LOJ6031][雅礼集训2017Day1]字符串-后缀自动机-莫队算法-倍增LCA

字符串

Description

s w 为两字符串,定义:

w[l,r] 表示字符串 w 在区间 [l,r] 中的子串;
w s 中出现的频率定义为 w s中出现的次数;
f(s,w,l,r) 表示 w[l,r] s 中出现的频率。
比如 f(ababa,aba,1,3)=2

现在给定串 s m 个区间 [l,r] 和长度 k ,你要回答 q 个询问,每个询问给你一个长度为 k 的字符串 w 和两个整数 a,b ,求:

i=abf(s,w,li,ri)

Input

第一行四个整数 n,m,q,k n 表示 s 的长度。
接下来一行一个长为 s 的字符串 s
接下来 m 行,每行两个整数表示 li,ri​​ 。
接下来 q 行,每行一个字符串 w,两个整数 a,b

Output

对于每个询问一行,输出答案。

Sample Input

8 5 3 3
abacdaba
0 2
1 2
0 0
2 2
1 2
dab 1 4
bac 2 3
eeb 1 3

Sample Output

7
3
2

Hint

对于 10% 的数据, n,m,k,q10
对于 30% 的数据,满足 n,m,k,q102
对于 50% 的数据,满足 n,m,k,q104
对于 100% 的数据,满足 n,m,k,q105,w105 ,字符串由小写英文字母构成。


神题不解释……
蒟蒻只能想到前一半莫队…..
然而当时考的时候连后缀自动机都不会

另外咱很久没打后缀自动机以及莫队了,今天两个方法按定义脑补居然都一遍过了……真不可思议……


思路:
咱要是说乍一看毫无思路呢……

好吧,事实上,看题目要求出现次数就可以看出来需要后缀自动机了。
然后显然咱需要right集合的大小。如果不知道right集合是什么那咱也没办法。

然后呢?

难以想到对所有数据通用的算法,考虑分部分针对数据设计算法。
观察到 w105 ,考虑据此设计针对性算法。

先考虑 k 较小的情况。
特点是串的数量很多但长度不大,需采用同时回答大量询问,但允许k影响较大的算法。

考虑一个奇妙的方法:莫队。
可以发现,咱可以采用 O(k2) 的时间计算所有区间的贡献。
如果此时咱用莫队离线维护最后的那些询问,每次记一个 cnt[i][j] 表示之前的询问中有多少管辖区间为 [i,j]
那么每次 O(k2) 枚举每个区间,每个区间得到的答案乘上有几个询问覆盖了此区间就是这个区间的贡献~

复杂度 O(n1.5k2)
发现小数据快如闪电……

然而k一大就尴尬了。
考虑这样的数据的特征:串的数量少。
那么可以考虑使用一个复杂度与 k 关系较小的算法,要求快速处理单次询问,与长度关系较小。

这里使用了倍增。
可以发现匹配原串的某个前缀的某个后缀(也就是某个子串),只需要从这个前缀当前所在端点开始,不断沿fail指针跳,直到调到的节点长度恰好等于这个后缀的长度,此时所在节点的right的大小即为答案。
那么考虑到fail指针连成了一棵叫做fail树的东西,用倍增维护这棵树进行查询即可~
具体做法是,对于每个后面的询问,在后缀自动机上跑一边原串,每走到一个字符便使用倍增查询所有右端点为这个字符的之前的询问即可~

复杂度O(q(k+mlog2k))

sqrt(n) 附近的某个值作为两种算法的使用分界线,小于用莫队,大于用倍增即可~
看着是不是一股不能过的气息……然而跑得飞快~

下面那个mo是莫队,但倍增咱不知道叫什么于是随手故意取了一个~

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

typedef long long ll;
const int N=1e5+9;
const int K=23;
const int M=26;
const int border=519;

int n,m,q,k;
char s[N];

namespace sam
{
    int ch[N<<1][M],fa[N<<1][K],tot,u;
    int len[N<<1],siz[N<<1],id[N<<1];

    inline void init()
    {
        u=tot=1;
    }

    inline void add(int v)
    {
        int now=++tot;
        len[now]=len[u]+1;
        siz[now]=1;

        while(u && !ch[u][v])
            ch[u][v]=now,u=fa[u][0];

        if(!u)
            fa[now][0]=1;
        else
        {
            int q=ch[u][v];
            if(len[q]==len[u]+1)
                fa[now][0]=q;
            else
            {
                int newq=++tot;
                memcpy(ch[newq],ch[q],sizeof(ch[q]));
                len[newq]=len[u]+1;
                fa[newq][0]=fa[q][0];
                fa[q][0]=fa[now][0]=newq;

                while(u && ch[u][v]==q)
                    ch[u][v]=newq,u=fa[u][0];
            }
        }
        u=now;
    }

    inline void work()
    {
        static int cnt[N];
        for(int i=1;i<=tot;i++)
            cnt[len[i]]++;
        for(int i=1;i<=n;i++)
            cnt[i]+=cnt[i-1];
        for(int i=1;i<=tot;i++)
            id[cnt[len[i]]--]=i;
        for(int i=tot;i>=1;i--)
            siz[fa[id[i]][0]]+=siz[id[i]];
    }

    inline void build()
    {
        fa[1][0]=0;
        for(int i=1;i<K;i++)
            for(int j=1;j<=tot;j++)
                fa[j][i]=fa[fa[j][i-1]][i-1];
    }

    inline int nxt(int &now,int v,int dir)
    {
        if(ch[now][dir])
        {
            now=ch[now][dir];
            return v+1;
        }
        while(now && !ch[now][dir])
            now=fa[now][0];
        v=len[now];
        if(ch[now][dir])
            v++;
        now=ch[now][dir];
        if(now==0)now=1;    
        return v;
    }

    inline ll query(int now,int val)
    {
        if(len[now]<val)return 0;
        for(int i=K-1;i>=0;i--)
            if(len[fa[now][i]]>=val)
                now=fa[now][i];
        return siz[now];
    }
}

struct query
{
    int l,r;
}qu[N];

namespace mo
{
    using namespace sam;

    struct querys
    {
        int l,r,id;
    }que[N];

    ll ans[N];
    int cnt[border+1][border+1];
    char w[N][border+1];

    bool cmp(const querys &a,const querys &b)
    {
        if(a.l/border==b.l/border)
            return a.r<b.r;
        return a.l/border<b.l/border;
    }

    int main()
    {
        for(int i=1;i<=q;i++)
        {
            scanf("%s",w[i]+1);
            que[i].l=read()+1;
            que[i].r=read()+1;
            que[i].id=i;
        }

        sort(que+1,que+q+1,cmp);

        int l=0,r=0;
        for(int i=1;i<=q;i++)
        {
            while(l<que[i].l)
                cnt[qu[l].l][qu[l].r]--,l++;
            while(que[i].l<l)
                cnt[qu[l-1].l][qu[l-1].r]++,l--;
            while(r<que[i].r)
                cnt[qu[r+1].l][qu[r+1].r]++,r++;
            while(que[i].r<r)
                cnt[qu[r].l][qu[r].r]--,r--;
            for(int s=1;s<=k;s++)
            {
                int now=1;
                for(int t=s;t<=k;t++)
                {
                    now=ch[now][w[que[i].id][t]-'a'];
                    if(!now)continue;
                    ans[que[i].id]+=(ll)cnt[s][t]*siz[now];
                }
            }
        }

        for(int i=1;i<=q;i++)
            printf("%lld\n",ans[i]);
        return 0;
    }
}

namespace ha
{
    char w[N];
    vector<int> wea[N];

    int main()
    {
        sam::build();
        for(int cases=1,l,r;cases<=q;cases++)
        {
            scanf("%s",w+1);
            l=read()+1;r=read()+1;

            for(int i=l;i<=r;i++)
                wea[qu[i].r].push_back(qu[i].r-qu[i].l+1);

            ll ans=0;
            int now=1,val=0;
            for(int i=1;i<=k;i++)
            {
                val=sam::nxt(now,val,w[i]-'a');
                for(int j=0;j<wea[i].size();j++)
                {
                    if(val<wea[i][j])continue;
                    ans+=sam::query(now,wea[i][j]);
                }
                wea[i].clear();
            }

            printf("%lld\n",ans);
        }

        return 0;
    }
}

int main()
{
    n=read();m=read();
    q=read();k=read();

    scanf("%s",s+1);
    sam::init();
    for(int i=1;i<=n;i++)
        sam::add(s[i]-'a');
    sam::work();

    for(int i=1;i<=m;i++)
        qu[i].l=read()+1,qu[i].r=read()+1;
    if(k<border)
        return mo::main();
    else
        return ha::main();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值