bzoj2787 Spoj COT4 (11482). Count on a trie

先给个这道题的链接:spoj bzoj

最近我重新复(学)习了一遍后缀自动机,发现原来有好多的东西已经全都忘掉了,这道题是我做kuangbin专题加强版时遇到的最难的一道题,也是到目前为止基本上是我写过的代码里最长的一个(树套树都没这么长啊QAQ),写了我整整一天

我做的专题是:打个广告,好像打不开,网址是 https://vjudge.net/contest/214869#overview

代码长度:压行以后,把debug的全删掉250行,加上就320行了,如果format一下就更长了,快400行了吧

时间:bzoj现在排名第4,2100ms,vjudge(spoj)上570ms,现在是第2,还是比较快的(orz rank1的大佬,好像是基本一样的方法,但是看都看不懂)

update:加了个输入挂,然后spoj->260ms,加了个输出挂,然后spoj->250ms,bzoj->1412ms,全都rank1了好爽啊,想要输入输出挂代码的可以去vjudge上找下,rank1感觉可能是由于机器升级了,这道题又没啥人做造成的

做法:主要参考了两个blog:blog1 blog2 还有这个blog3

虽然我由于我太菜,基本思路全都是参考的第二个blog,感觉写的都和第二个blog里的都差不多了,这里可以认为基本上是对那个blog的补充

题意我就不详细说了,主要是说:

两组串的set,S里是格式串,T串是目标串,求T中某串Ti在S中Si出现了几次

S串的增加方法是从一个已有的串往后加个字符,生成新串

T串的增加方法是从一个已有的串向前或者向后加个字符;或者两个已有的串连到一块生成新串

详细讲讲做法:(在线的我实在是不会啊,感觉怎么也不能直接做,或许后缀平衡树用替罪羊树均摊可做?感觉不太对的样子)

首先,我们知道一个定理,后缀自动机的fail树是反串的后缀树,然后我们利用这个定理来做这道题:

在线做的话,S串和T串都是会变化的,虽然S串变化不大,但是离线显然会简化问题:

然后我们就离线做了:

由于:第一,一个字符很简单就能找到在任意串出现的次数,第二,T串的增加方式和S串是一样的,所以我们只需要解决一个问题:两个串Ta和Tb,而且你知道Ta串在任意串中出现的次数和Tb串在任意串出现的次数,怎样维护加起来的串在任意串中才出现的次数呢?(这里维护任意串是因为如果只维护一个串或者某些串,我不知道怎样能让复杂度是对的)

这里我自己觉得只有hash一下才可以做,然后就GG了,但是其实有个如果从后缀树来反着看的话,有一些性质:

首先,如果将trie的串从小到大加到后缀自动机上的话,有个性质就是加入的点fail上长度是不一样的,也就是说从fail网往上看,每个点代表的串都不同,而且fail树上从0往下看每个开始的字符一定不同,否则就矛盾了

然后如果我们在trie上bfs,同时加进后缀自动机,就能够把trie按后缀排序了(这可能是个套路,然后我太菜并不知道)

那么我们对于T串的每一个串维护一个[L,R],表示作为一个串能在之前的后缀上的rank,然后合并时二分一下,然后在trie上求kth-father,就能够得出连起来的[L',R'] 了(二分然后将前串的rank和kth-father的比一比,这里的二分写的我好难受啊)

然后,由于这里求father比较多,或者说特别多,如果这里倍增来求kth-father,常数较大,而且多个log,坑爹的卡常(或者本意就是要卡这个log),导致过不了这道题。如果用32位的预处理是可过的,但是有更好的方法:kth-father有个利用长链剖分,O(nlogn)预处理,O(1)查询的ladder+倍增的方法,用这个方法就能过了(我写的时候还写挂了好长时间。。注意ladder只有top相等时要push),链接:zqc的blog

下面给下我的代码(为了清楚,不带输入输出挂)

代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <cstring>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <utility>
#include <cassert>
using namespace std;
#define REP(I,N) for (I=0;I<N;I++)
#define rREP(I,N) for (I=N-1;I>=0;I--)
#define rep(I,S,N) for (I=S;I<N;I++)
#define rrep(I,S,N) for (I=N-1;I>=S;I--)
#define FOR(I,S,N) for (I=S;I<=N;I++)
#define rFOR(I,S,N) for (I=N;I>=S;I--)
typedef unsigned long long ULL;
typedef long long LL;
const int INF=0x3f3f3f3f;
const LL INFF=0x3f3f3f3f3f3f3f3fll;
const LL M=1e9+7;
const LL maxn=3e5+7;
const double eps=0.00000001;
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
template<typename T>inline T abs(T a) {return a>0?a:-a;}
template<typename T>inline T powMM(T a,T b){T ret=1;for (;b;b>>=1ll,a=1ll*a*a%M) if (b&1) ret=1ll*ret*a%M;return ret;}

//SPOJ COT4
struct SAM{
    const static int maxn=2e5+7;
    int next[maxn][26],fail[maxn],len[maxn],cnt,pos[maxn],Pos[maxn];
    void init(){
        cnt=0;fail[0]=-1;len[0]=0;pos[0]=0;
        memset(next[0],0,sizeof(next[0]));
    }
    int add(int p,int c,int id){
        int np=++cnt;pos[np]=Pos[np]=id;
        memset(next[np],0,sizeof(next[np]));
        len[np]=len[p]+1;
        for (;p!=-1&&!next[p][c];p=fail[p]) next[p][c]=np;
        if (p==-1) fail[np]=0;
        else {
            int q=next[p][c];
            if (len[p]+1==len[q]) fail[np]=q;
            else{
                int nq=++cnt;len[nq]=len[p]+1;
                memcpy(next[nq],next[q],sizeof(next[q]));
                fail[nq]=fail[q];pos[nq]=pos[q];Pos[nq]=0;
                fail[np]=fail[q]=nq;
                for (;p!=-1&&next[p][c]==q;p=fail[p]) next[p][c]=nq;
            }
        }
        return np;
    }
    int failnext[maxn][26];
};
struct TRIE{
    SAM sam;
    const static int maxn=1e5+26+7;
    void init(){
        sam.init();
        tot=0;ToT=1;id[ToT]=0;val[0]=-1;//1:空
        memset(next[0],0,sizeof(next[0]));
    }
    //1:trie上建树
    int id[maxn],ToT;//queries
    int next[maxn][26],last[maxn],tot;//on the trie
    int val[maxn];
    void Add(int i,int c){
        int p=id[i];ToT++;
        if (!next[p][c]) {
            next[p][c]=++tot;val[tot]=c;
            memset(next[tot],0,sizeof(next[tot]));
            fa[tot][0]=p;
        }id[ToT]=next[p][c];
    }
    int Q[maxn],st,ed;
    void buildSAM(){
        st=ed=0;Q[ed++]=0;
        while (st!=ed){
            int p=Q[st++];char c;
            REP(c,26) if (next[p][c]){
                int nxt=next[p][c];
                last[nxt]=sam.add(last[p],c,nxt);
                Q[ed++]=nxt;
            }
        }
    }
    //2:get L-R
    int failtot;
    int rank[maxn],sa[maxn];
    void dfsrank(int x){
        if (sam.Pos[x]) rank[sam.Pos[x]]=++failtot,sa[failtot]=sam.Pos[x];
        char c;
        REP(c,26) if (sam.failnext[x][c]) dfsrank(sam.failnext[x][c]);
    }
    void linkfail(){
        int i;
        memset(sam.failnext,0,sizeof(sam.failnext[0])*(sam.cnt+1));
        FOR(i,1,sam.cnt)
            sam.failnext[sam.fail[i]][val[prev(sam.pos[i],sam.len[sam.fail[i]])]]=i;
        dfsrank(0);
    }
    //3:build_fa; ladder长链剖分
    int fa[maxn][21],son[maxn],top[maxn],len[maxn],dep[maxn];
    vector<int> ladder[maxn],upper[maxn];
    int upp[maxn];
    void buildfa(){
        int i,j,c;
        dep[0]=0;
        FOR(i,1,tot) rep(j,1,21)
            fa[i][j]=fa[fa[i][j-1]][j-1],dep[i]=dep[fa[i][0]]+1;
        rFOR(i,0,tot){
            int o=0;top[i]=i;
            ladder[i].clear();
            REP(c,26) if (next[i][c]){
                int p=next[i][c];
                if (!o||len[o]<len[p]) o=p;
            }if (o) len[i]=len[o]+1;else o=0;
            son[i]=o;top[i]=i;
        }FOR(i,0,tot) if (son[i]) top[son[i]]=top[i];
        rFOR(i,0,tot) ladder[top[i]].push_back(i);
        FOR(i,1,tot) if (top[i]==i){
            int u=i;
            REP(j,len[i]){
                u=fa[u][0];
                ladder[i].push_back(u);
                if (!u) break;
            }
        }upp[0]=-1;
        FOR(i,1,tot) upp[i]=upp[i-1]+(i==(i&-i));
    }
    int prev(int x,int k){;
        if (!k) return x;
        x=fa[x][upp[k]];k-=1<<upp[k];
        k-=dep[x]-dep[top[x]];x=top[x];
        return ladder[x][len[x]+k];
    }
}trie;
struct queries{
    int op,i,j;char c;
}q[maxn];
//3:get Ans_L-R
int QAQ;
struct node{
    int l,r,len,next;
    node(){l=r=len=0;}
    node(int _l,int _r,int _len):l(_l),r(_r),len(_len){};
}A[maxn],C[27];
node merge(node A,node B){//反着来的,B在后
    if (A.len==0) return B;
    if (B.len==0) return A;
    if (B.l>B.r||A.l>A.r) return node(0,-1,A.len+B.len);
    int l,r,L,R;
    l=B.r+1;r=B.l-1;
    for(L=B.l,R=B.r;L<=R;){
        int mid=(L+R)/2;
        if (trie.rank[trie.prev(trie.sa[mid],B.len)]<A.l) L=mid+1;
        else R=mid-1,l=mid;
    }
    for(L=B.l,R=B.r;L<=R;){
        int mid=(L+R)/2;
        if (trie.rank[trie.prev(trie.sa[mid],B.len)]>A.r) R=mid-1;
        else L=mid+1,r=mid;
    }
    return node(l,r,A.len+B.len);
}
//4:solve
int F[maxn];
inline int lowbit(int x){return x&-x;}
void update(int x,int val){
    for (;x<=trie.failtot;x+=lowbit(x)) F[x]+=val;
}int query(int x){
    int ret=0;
    for (;x;x-=lowbit(x)) ret+=F[x];
    return ret;
}int query(int l,int r){
    if (l>r) return 0;
    return query(r)-query(l-1);
}
node B[maxn];
int Ans[maxn],head[maxn];
void addnode(int x,int pos,int i){
    x=trie.id[x];B[i]=A[pos];
    B[i].next=head[x];head[x]=i;
}
void getans(int x){
    int i;
    if (x) update(trie.rank[x],1);
    for (i=head[x];~i;i=B[i].next){
        if (B[i].len&&B[i].l<=B[i].r) Ans[i]=query(B[i].l,B[i].r);
        else Ans[i]=0;
    }
    REP(i,26) if (trie.next[x][i]) getans(trie.next[x][i]);
    if (x) update(trie.rank[x],-1);
}
int n,m,Q;
int i,j,k;
char c;
int main(){
    scanf("%d",&Q);
    trie.init();
    FOR(i,1,Q){
        scanf("%d",&q[i].op);
        if (q[i].op==1){
            scanf("%d %c",&q[i].i,&c);q[i].c=c-'a';
            trie.Add(q[i].i,q[i].c);
        }else if (q[i].op==2){
            scanf("%d%d %c",&q[i].i,&q[i].j,&c);q[i].c=c-'a';
        }else scanf("%d%d",&q[i].i,&q[i].j);
    }
    trie.buildfa();
    trie.buildSAM();
    trie.linkfail();
    REP(i,26){
        int l,r,L,R;
        l=trie.failtot+1;r=0;
        for(L=1,R=trie.failtot;L<=R;){
            int mid=(L+R)/2;
            if (trie.val[trie.sa[mid]]<i) L=mid+1;
            else R=mid-1,l=mid;
        }
        for(L=1,R=trie.failtot;L<=R;){
            int mid=(L+R)/2;
            if (trie.val[trie.sa[mid]]>i) R=mid-1;
            else L=mid+1,r=mid;
        }
        C[i]=node(l,r,1);
    }
    FOR(i,0,trie.tot) head[i]=-1;QAQ=1;
    FOR(i,1,Q){
        if (q[i].op==1){
            head[i]=-1;
        }else if (q[i].op==2){
            if (q[i].i==0) A[++QAQ]=merge(C[q[i].c],A[q[i].j]);
            if (q[i].i==1) A[++QAQ]=merge(A[q[i].j],C[q[i].c]);
        }else if (q[i].op==3)
            A[++QAQ]=merge(A[q[i].i],A[q[i].j]);
        else addnode(q[i].j,q[i].i,i);
    }
    getans(0);
    FOR(i,1,Q) if (q[i].op==4) printf("%d\n",Ans[i]);
    return 0;
}
/*
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值