HDU 5069 Harry And Biological Teacher AC自动机 + 线段树优化

题解直接看大佬博客

诶,可能是能力不够,还是不是很懂,下次再战

建议模拟一遍,
来点图,结合代码,加深印象

样例一:
两个模式串:ACCGT 和 TTT

建立所有节点的fail指针
get_fail

样例二:
图不画了,参考样例一
在这里插入图片描述
根据fail树,建立时间戳
dfs的顺序
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述


#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int n,m,k;

struct Segment_Tree{
    #define lson l,mid,rt<<1
    #define rson mid+1,r,rt<<1|1
    int val[N<<2];
    bool flag[N<<2];//懒标记

    void pushup(int rt){
        val[rt]=max(val[rt<<1],val[rt<<1|1]);
        flag[rt]=0;//清空懒标记
    }

    void build(int l,int r,int rt){
        if(l==r){
            val[rt]=flag[rt]=0;
            return;
        }
        int mid=l+r>>1;
        build(lson);
        build(rson);
        pushup(rt);
    }

    void pushdown(int rt){
        if(flag[rt]){
            flag[rt<<1]=flag[rt<<1|1]=1;//向下传递
            flag[rt]=0;

            val[rt<<1]=max(val[rt<<1],val[rt]);
            val[rt<<1|1]=max(val[rt<<1|1],val[rt]);
            //因为时间戳 父区间不可能再更新 //val[rt]的更新在pushup里
        }
    }

    void update(int l,int r,int rt,int L,int R,int value){
        if(L<=l && r<=R){
            val[rt]=max(val[rt],value);
            flag[rt]=1;
            return;
        }
        pushdown(rt);
        int mid=l+r>>1;
        if(L<=mid)update(lson,L,R,value);
        if(R>mid)update(rson,L,R,value);
        //pushup(rt);不可以更新 会影响到前面的时间戳的
    }

    int query(int l,int r,int rt,int pos){
        if(l==r){
            return val[rt];
        }
        pushdown(rt);
        int mid=l+r>>1;
        if(pos<=mid)return query(lson,pos);
        if(pos>mid)return query(rson,pos);
        return -INF;//应对一个不存在的?? 会用到吗?
    }

}seg;
struct query{
    int a,b,id;
}que[N];
bool cmp(query x,query y){//自动机上由a走到b
    if(x.b==y.b) return x.a<y.a;
    return x.b<y.b;
}

struct Tire{
   int next[N][4];
   int fail[N];
   int pos[N];//记录每个字符串在AC自动机上的结尾的位置

   int L,root;
    vector<string> v;//记录所有模式串

   int NewNode(){//init
       for (int i = 0; i <4; ++i) {
           next[L][i]=-1;
       }
       L++;
       return L-1;
   }

   void init(){
       L=0;
       root=NewNode();
       v.clear();
   }

   int encode(char c){
       if(c=='A')return 0;
       if(c=='G')return 1;
       if(c=='C')return 2;
       if(c=='T')return 3;
       return -1;
   }

   void insert(int id){//build 将字符串添加到树上
       int now=root;
       for (int i = 0,len=v[id].size();i<len; ++i) {
           int x=encode(v[id][i]);
            if(next[now][x]==-1)
                next[now][x]=NewNode();
            now=next[now][x];
       }
       pos[id]=now;
   }

   void build(){//get_fail
       fail[root]=root;
       queue<int>q;
       q.push(root);

       while(!q.empty()){
           int now=q.front();
           q.pop();

           for (int i = 0; i <4; ++i) {
               if(next[now][i]==-1){//子节点不存在
                   next[now][i]=(now==root?root:next[fail[now]][i]);
               }else{//子节点存在
                   fail[next[now][i]]=(now==root?root:next[fail[now]][i]);
                   q.push(next[now][i]);
               }
           }
       }
   }

   int time;//时间戳
   vector<int> g[N];//存树
   int left[N],right[N];//每个节点对应的线段树的时间戳 [ left[i], right[i] ]

   void dfs(int now){//更新每个节点的时间戳
       left[now]=++time;
       for (int i = 0, sz = g[now].size(); i < sz; ++i) {
           dfs(g[now][i]);
       }
       right[now]=time;
   }

   void failTree(){
       for (int i = 0; i < L; ++i) {
            g[i].clear();
       }
       for (int i = 1; i <L; ++i) {
           g[fail[i]].push_back(i);//建 fail8 tree
       }
       time=0;
       dfs(root);//获取时间戳
   }

   int ans[N];
   void solve(){
       for (int i = 1; i <= m; ++i) {//离线
           cin>>que[i].a>>que[i].b;
           que[i].a--;//与vector的存储顺序一一对应
           que[i].b--;
           que[i].id=i;
       }
       sort(que+1,que+1+m,cmp);
       failTree();//建 fail tree
       seg.build(1,time,1);//以时间戳为轴更新线段树
       int base=0;//基准
       for (int i = 1; i <= m; ++i) {
           int nowb=que[i].b;
           int now=root;//每次都从根节点出发
           for (int j = 0, sz = v[nowb].size(); j < sz; ++j) {
               now=next[now][encode(v[nowb][j])];
               seg.update(1,time,1,left[now],right[now],j+1+base);//val和j有关
           }
           while(i<=m && que[i].b==nowb){//相同结尾的 直接统计 因为第一遍已经处理到结尾了 
               ans[que[i].id]=max(0,seg.query(1,time,1,left[pos[que[i].a]])-base);
               											//从结尾(后缀)开始查询匹配
               											//如果没有相同部分 返回的是个非正数 答案就是0了
               i++;
           }
           i--;
           base+=v[nowb].size()+1;//对于下一组相同的b, base提升一个基值, 这样就不用初始化线段树了
       }
		//输出答案
       for (int i = 1; i <= m; ++i) {
           cout <<ans[i] << endl;
       }
   }

}AC;

int main(){
    ios::sync_with_stdio(0);//不开必T
    while(cin>>n>>m){
        AC.init();
        string s;
        for (int i = 0; i <n; ++i) {
            cin>>s;
            AC.v.push_back(s);//保存字符串
            AC.insert(i);//build(s)
        }
        AC.build();//get_fail
        AC.solve();
    }
    return 0;
}
发布了34 篇原创文章 · 获赞 0 · 访问量 949

猜你喜欢

转载自blog.csdn.net/Yubing792289314/article/details/104190845