zoukankan      html  css  js  c++  java
  • NOI2018 Day 1 你的名字

    在打网络同步赛的时候自己还不会后缀自动机,这题就写了个hash暴力滚粗。

    为了提高自己的姿势水平,就学习了后缀自动机。

    首先,这题的68分算法十分好想,有很多种写法。

    100分算法的流程如下:

    1.对S串建后缀自动机,线段树合并求出每个点的right集合

    2.对读入的T串在S的自动机上跑,尽可能地跳fa,直到p所代表的某个串在S的区间中出现过,给其打上标记

    3.将t插入后缀自动机,用标记维护答案。

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int _S_=500010,INF=INT_MAX>>2; 
    namespace sama{
        struct p{
            int trans[26],fa,ml,val;
        }sa[_S_*2];
        int tot,la,in[_S_*2];
        void init(){
            la=1;
            for (int i=1; i<=tot; ++i) memset(sa[i].trans,0,sizeof(sa[i].trans));
            tot=1;
        }
        void insert(int c,int v){
            int np=++tot,p=la; sa[np].ml=sa[la].ml+1;
            for (; p&&!sa[p].trans[c]; p=sa[p].fa) sa[p].trans[c]=np;
            if (p){
                int q=sa[p].trans[c];
                if (sa[q].ml==sa[p].ml+1) sa[np].fa=q;
                else{
                    int nq=++tot; sa[nq].ml=sa[p].ml+1; sa[nq].val=sa[q].val;
                    memcpy(sa[nq].trans,sa[q].trans,sizeof(sa[q].trans));
                    sa[nq].fa=sa[q].fa; sa[q].fa=sa[np].fa=nq;
                    for (; p&&sa[p].trans[c]==q; p=sa[p].fa) sa[p].trans[c]=nq; 
                } 
            }
            else sa[np].fa=1;
            sa[la=np].val=v;
        }
        long long query(){
            long long ans=0;
            for (int i=1; i<=tot; ++i)
                if (sa[i].ml>sa[i].val) ans+=sa[i].ml-max(sa[i].val,sa[sa[i].fa].ml);
            return ans;
        }
    }
    int la=1,tot=1,l,sl,sr,cc,trans[_S_*2][26],ml[_S_*2],fa[_S_*2],in[_S_*2];
    void append(int c){
        int np=++tot,p=la; ml[np]=ml[la]+1;
        for (; p&&!trans[p][c]; p=fa[p]) trans[p][c]=np;
        if (p){
            int q=trans[p][c];
            if (ml[q]==ml[p]+1) fa[np]=q;
            else{
                int nq=++tot; ml[nq]=ml[p]+1;
                memcpy(trans[nq],trans[q],sizeof(trans[q]));
                fa[nq]=fa[q]; fa[q]=fa[np]=nq;
                for (; p&&trans[p][c]==q; p=fa[p]) trans[p][c]=nq; 
            }
        }
        else fa[np]=1;
        la=np;
    }
    namespace segmenttree{
        const int NODE=_S_*20*2;
        int rt[_S_*2],ll,rr,num;
        struct node{
            int l,r,v;
        }T[NODE];
        #define M int mid=(l+r)>>1
        #define L (T[ind].l,l,mid)
        #define R (T[ind].r,mid+1,r)
        inline void pushup(int x){
            T[x].v=(T[x].l?(T[x].r?max(T[T[x].l].v,T[T[x].r].v):T[T[x].l].v):T[T[x].r].v);
        }
        inline void insert(int &ind,int l,int r){
            if (!ind) ind=++num;
            if (l==r) return void(T[ind].v=ll);
            M;
            ll<=mid?insert L:insert R;
            pushup(ind);
        }
        inline int ask(int ind,int l,int r){
            if (!ind) return -INF;
            if (ll<=l&&r<=rr) return T[ind].v;
            M;
            return ll<=mid?(mid<rr?max(ask L,ask R):ask L):ask R;
        }
        inline int merge(int x,int y){
            if (!(x&&y)) return x^y;
            int ind=++num;
            T[ind].l=merge(T[x].l,T[y].l);
            T[ind].r=merge(T[x].r,T[y].r);
            pushup(ind);
            return ind;
        }
    }
    using namespace segmenttree;
    void topsort(){
        queue<int> q;
        for (int i=1; i<=tot; ++i) in[i]=0;
        for (int i=1; i<=tot; ++i) ++in[fa[i]];
        for (int i=1; i<=tot; ++i) if (!in[i]) q.push(i);
        while (!q.empty()){
            int x=q.front(); q.pop();
            if (fa[x]){
                rt[fa[x]]=merge(rt[fa[x]],rt[x]);
                if (!--in[fa[x]]) q.push(fa[x]);
            }
        }
    }
    void walk(int c){
        int p=la,t=-INF,ban=-INF;
        for (; p&&!trans[p][c]; p=fa[p]) l=ml[fa[p]];
        if (p){
            p=trans[p][c];
            ++l;
            while (p>1){
                ll=sl+ml[fa[p]]; rr=sr;
                if (ll<=rr&&(t=ask(rt[p],1,cc))!=-INF) break;
                p=fa[p];
                l=ml[p];
            }
            ban=min(t-sl+1,l);
        }
        else p=1; 
        la=p;
        sama::insert(c,ban);
    }
    char s[_S_];
    int main(){
        freopen("name.in","r",stdin); freopen("name.out","w",stdout); 
        ios::sync_with_stdio(0);
        cin>>(s+1); cc=strlen(s+1);
        for (int i=1; s[i]; ++i){
            append(s[i]-'a');
            ll=i; insert(rt[la],1,cc);
        }
        topsort();
        int q; cin>>q;
        while (q--){
            cin>>(s+1)>>sl>>sr;
            la=1; l=0; ll=0;
            sama::init();
            for (int i=1; s[i]; ++i) walk(s[i]-'a');
            cout<<sama::query()<<endl;
        }    
    }

    具体实现的时候有一些细节,比如最长的在S中出现的串长不能超过T在S中的匹配长度,初始值的设定等。

    另外,之所以T在跳S的后缀自动机的fa时,可以直接修改当前节点,是因为如果一个节点代表的所有串当前没有出现在S的指定区间出现过且最小串长大于1,那么加入一个新字符后也不可能出现。

    如果最小串长等于1,显然一直跳到root也没有什么问题。

    在开动态开点线段树的空间时,很容易忽略掉线段树合并所带来的常数2,需要小心。

    sam能做的事情好多啊!

  • 相关阅读:
    CodeForces 1294B Collecting Packages(排序+贪心)
    计算系数(组合数)
    垒骰子(矩阵快速幂)
    蒜头君倒水(矩阵快速幂)
    Buy Tickets POJ
    Billboard HDU
    树状数组求逆序对 附HDU1394
    codeforces 1304D Shortest and Longest LIS
    codeforces 1301C Ayoub's function
    Codeforces 1301B Motarack's Birthday(二分)
  • 原文地址:https://www.cnblogs.com/Yuhuger/p/9453882.html
Copyright © 2011-2022 走看看