Gym - 101741K Consistent Occurrences

题意: 给出一个长度为 n n 的主串(n<1e5),接下来输入 m m (m<1e5)次询问,每次输入一个 Si S i (iSi<=1e5) ( ∑ i S i <= 1 e 5 ) ,询问在主串中 Si S i 不出现重叠的前提下最多出现了多少次。

做法: 因为太菜了不会AC自动机,就写了一发后缀自动机,然后wa了一下午,心态爆炸。晚上找错误的时候把空间开成了2倍,A了… 气的受不了,那么过分一定要写在博客上。
考虑后缀自动机,一般情况下后缀自动机处理可重复串比较多,遇到了不能重复的顿时就懵了。假如我们能维护出对于每一个自动机上的节点的 right r i g h t 集合,那么对于每一次询问就可以自己从对应自动机上的 right r i g h t 集一直二分跳着查找就行了。假如可以处理出来 right r i g h t 集这样好像还是不行,例如主串1e5个a,1e5个询问每次都一个a,毫无疑问复杂度会爆炸。那么考虑离线处理询问,我们对于每一个询问在自动机上找到对应终止节点,标记上需要该节点需要询问的字符串长度(即每两个 right r i g h t 之间的最小间隔),当然对于一个节点可能需要处理的询问有多个,并且可能重复,于是我对每个节点都开了 set s e t 维护。这样总复杂度最差应该也不会达到 n+n/2+n/3+.... ( n + n / 2 + n / 3 + . . . . ) 约为nlogn

那么对于每一个节点维护right集最差情况下好像也会 n2 n 2 ,那么我们拓扑排序后启发式合并(就是把小集合塞大集合)因为对于处理每一个节点时他的后面节点的信息就没必要存储了,空间复杂度也是绝对ok的。

因为也是第一次用启发式合并 right r i g h t 集,顺利过了当涨姿势记一下。
代码:

#include <bits/stdc++.h>
using namespace std;

const int Max = 2e5+5;

struct sam{
    int pa[Max<<1], son[Max<<1][27], deep[Max<<1], cnt, root, last;//自动机相关
    set<int> R[Max<<1];//用来维护每个节点的right集
    inline int Newnode(int _deep){deep[++cnt]=_deep;return cnt;}
    inline void pre(){root=last=Newnode(0);}
    inline void SAM(int alp){
        int np=Newnode(deep[last]+1);
        int u=last;//R[np].insert(deep[np]);这样对于集合赋予初值也是正确的
        memset(son[np], 0, sizeof(son[np]));
        while(u&&!son[u][alp])son[u][alp]=np, u=pa[u];
        if(!u)pa[np]=root;
        else {
            int v=son[u][alp];
            if(deep[v]==deep[u]+1)pa[np]=v;
            else {
                int nv=Newnode(deep[u]+1);
                memcpy(son[nv], son[v], sizeof(son[v]));
                pa[nv]=pa[v], pa[v]=pa[np]=nv;
                while(u&&son[u][alp]==v)son[u][alp]=nv, u=pa[u];
            }
        }
        last=np;
    }
    int sum[Max<<1],tp[Max<<1];//拓扑排序相关
    inline void toposprt(){//拓扑排序
        for(int a=1; a<=deep[last]; a++)sum[a]=0;
        for(int a=1; a<=cnt; a++)sum[deep[a]]++;
        for(int a=1; a<=deep[last]; a++)sum[a]+=sum[a-1];
        for(int a=1; a<=cnt; a++)tp[sum[deep[a]]--]=a;
    }
    void init(){//初始化,用的时候先调用才行
        cnt=0;
        memset(son[1], 0, sizeof(son[1]));
        pre();
    }
}sam;
char str[Max<<1], pat[Max<<1];
struct node{
    int p, x, ans;
}Q[Max<<1];//离线询问,p:节点编号,x:询问串长度
int idx[Max<<1];//启发式合并相关
set<int> nes[Max<<1];//节点需要处理的串长度集合
vector<int> arr[Max<<1], cnt[Max<<1];//最后再对询问在节点上查询答案
int main(){
    //freopen("a.txt", "r", stdin);
    int n, m; sam.init();
    scanf("%d%d", &n, &m);
    scanf("%s", str);
    int len=strlen(str);
    for(int i=0; i<len; i++)sam.SAM(str[i]-'a');
    for(int i=0, p=1; i<len; i++){
        int u=str[i]-'a';
        p=sam.son[p][u];
        sam.R[p].insert(i+1);
    }
    for(int i=0; i<Max; i++)idx[i]=i;
    sam.toposprt();
    for(int i=0; i<m; i++)Q[i].ans=-1;
    for(int i=0; i<m; i++){//离线处理询问
        scanf("%s", pat);
        len=strlen(pat);
        int p=1;
        for(int j=0; j<len; j++){
            int u=pat[j]-'a';
            if(!sam.son[p][u]){
                Q[i].ans=0;break;
            }
            p=sam.son[p][u];
        }
        Q[i].p=p, Q[i].x=len;
    }
    for(int i=0; i<m; i++){
        if(Q[i].ans==0)continue;
        nes[Q[i].p].insert(Q[i].x);
    }
    for(int i=1; i<=sam.cnt; i++){
        for(auto x : nes[i]){
            arr[i].push_back(x);
        }
    }

    for(int i=sam.cnt; i>0; i--){//处理过程
        for(auto x : arr[sam.tp[i]]){//处理当前节点的询问
            if(sam.R[idx[sam.tp[i]]].size()==0){
                cnt[sam.tp[i]].push_back(0);
                continue;
            }
            int sum=0;
            auto tmp = sam.R[idx[sam.tp[i]]].begin();
            while(tmp!=sam.R[idx[sam.tp[i]]].end()){
                sum++; int kk=(*tmp)+ x;
                tmp = sam.R[idx[sam.tp[i]]].lower_bound(kk);
            }
            cnt[sam.tp[i]].push_back(sum);
        }
        if(sam.R[idx[sam.tp[i]]].size()<sam.R[idx[sam.pa[sam.tp[i]]]].size()){//启发式合并更新父亲节点
            for(auto x :sam.R[idx[sam.tp[i]]]){
                sam.R[idx[sam.pa[sam.tp[i]]]].insert(x);
            }
            sam.R[idx[sam.tp[i]]].clear();
        }
        else {
            for(auto x :sam.R[idx[sam.pa[sam.tp[i]]]]){
                sam.R[idx[sam.tp[i]]].insert(x);
            }
            sam.R[idx[sam.pa[sam.tp[i]]]].clear();
            idx[sam.pa[sam.tp[i]]]=idx[sam.tp[i]];
        }
    }
    for(int i=0; i<m; i++){
        if(Q[i].ans==0){
            printf("0\n");
            continue;
        }
        //每一次都去节点上查询答案
        int idx=lower_bound(arr[Q[i].p].begin(), arr[Q[i].p].end(), Q[i].x)-arr[Q[i].p].begin();
        printf("%d\n", cnt[Q[i].p][idx]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值