[航海协会]万灵药

万灵药

在这里插入图片描述
在这里插入图片描述

题解

首先容易发现,他题目中说的某两个前缀的最长公共后缀不就是我们正串 S A M SAM SAM上的节点吗?
但我们怎么求它们的最长公共前缀之和的答案呢?
有一种非常蠢的做法是,把我们反串的 S A M SAM SAM建出来,然后把正串上的所有结点在反串上建出它的虚树,显然,两个正串 S A M SAM SAM上的节点映射到反串 S A M SAM SAM节点的 l c a lca lca的深度就是它们的最长公共前缀的长度。

考虑一下上面的这个东西如何在加入删除的过程中维护答案。
显然,两个点割贡献可以看成这两个点到根的路径上重复的部分,那我们可以每加入一个点就把这个点到根路径上的每个点都加上 1 1 1,而一个点与别的点的贡献和就是它到根的这条路径上所有点的贡献之和。
删掉点的时候也只需要撤销掉它的贡献。
上面这东西显然是可以通过树链剖分来维护的。
这样是 O ( Q log ⁡ 2 n ) O\left(Q\log^2 n\right) O(Qlog2n)的。
好像过不了 n ⩽ 5 × 1 0 5 n\leqslant 5\times 10^5 n5×105,没事,可以发现我们的所有操作都是在树链上进行的,我们可以用全局平衡二叉树来维护,就成功达到 O ( n log ⁡ n ) O\left(n\log n\right) O(nlogn)了。注意一下,全局平衡二叉树事实上是平衡树的形式,而不是线段树,虽然两者复杂度上是没有区别的。

但笔者这样打了只会被疯狂卡常了。建两棵SAM还用倍增插点,不卡你卡谁
于是,笔者就去问了问神 O U Y E \rm O\color{red}{UYE} OUYE,原来最开始完全没必要像我们刚刚说的那样建正反两棵 S A M SAM SAM,然后再去插虚树。
可以发现,你建出来的虚树任意一条边的长度都不会超过 1 1 1
映射到原树上,如果点对 ( x , y ) (x,y) (x,y) l c a lca lca深度为 d ( d > 0 ) d(d>0) d(d>0),那么点对 ( x − 1 , y − 1 ) (x-1,y-1) (x1,y1) l c a lca lca深度肯定是 d − 1 d-1 d1,它们都会被加入到虚树里面去,也就是说,我们虚树里面的深度肯定是连续的,而且根本不会新建任何虚点,正串 S A M SAM SAM中的所有点就可以构成虚树。
待って,我们上面不是虚树里的边不都是相当于在原串的后面多了一个字符吗?就是我们上面说的 ( x − 1 , y − 1 ) → ( x , y ) (x-1,y-1)\rightarrow (x,y) (x1,y1)(x,y),这不就是正串 S A M SAM SAM里边的含义吗?
也就是说,正串 S A M SAM SAM D A G DAG DAG上刚好让长度加 1 1 1的边必然会出现在虚树里面,我们就可以直接通过正串 S A M SAM SAM构建出这棵树了。
再像上面说的那样建全局平衡二叉树即可。

时间复杂度 O ( n log ⁡ n ) O\left(n\log n\right) O(nlogn)

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef unsigned int uint;
#define MAXN 500005
#define MAXM 1000005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=998244353;
char gc(){static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
#define getchar gc
char obuf[1<<22],*opt=obuf+(1<<22);
void pc(const int&ch){*--opt=ch;}
#define putchar pc
template<typename _T>
void read(_T &x){
   _T f=1;x=0;char s=getchar();
   while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
   while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
   x*=f;
}
template<typename _T>
void print(_T x){putchar('\n');while(x>9){putchar((x%10)|'0');x/=10;}putchar(x|'0');}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,Q,dfn[MAXM],sz[MAXM],ltp[MAXM],idx,ip[MAXN],root[MAXM];
int father[MAXM],pre[MAXM],val[MAXM],wson[MAXM],dn[MAXM];LL ans,answer[MAXN];
char str[MAXN];bool vis[MAXM];
struct ming{int lson,rson,len,mid,lzy,dif;LL sm,sum;};
struct node{int ch[4],len,fa,ed;};
class SegmentTree{
    private:
        ming tr[MAXM];int tot;
        void work(int rt,int aw){
            tr[rt].sum+=1ll*aw*tr[rt].dif;
            tr[rt].sm+=aw;tr[rt].lzy+=aw;
        }
        void pushdown(int rt){
            if(tr[rt].lzy){
                if(tr[rt].lson)work(tr[rt].lson,tr[rt].lzy);
                if(tr[rt].rson)work(tr[rt].rson,tr[rt].lzy);
                tr[rt].lzy=0;
            }
        }
        void pushup(int rt){tr[rt].sum=tr[tr[rt].lson].sum+tr[tr[rt].rson].sum+tr[rt].sm;}
    public:
        void build(int &rt,int l,int r){
            rt=++tot;int mid,all=(sz[pre[l]]-sz[wson[pre[r]]]+1)/2;
            for(mid=l+1;mid<=r;mid++)if(sz[pre[l]]-sz[pre[mid]]>all)break;
            mid--;tr[rt].mid=mid;tr[rt].dif=r-l+1;
            if(l<mid)build(tr[rt].lson,l,mid-1);
            if(r>mid)build(tr[rt].rson,mid+1,r);
        }
        void insert(int rt,int l,int r,int al,int ar){
            if(al>r||ar<l||al>ar)return ;int mid=tr[rt].mid;
            if(al<=l&&r<=ar){ans+=tr[rt].sum;work(rt,1);return ;}
            if(al<=mid&&mid<=ar)ans+=tr[rt].sm,tr[rt].sm++;
            pushdown(rt);
            if(al<mid)insert(tr[rt].lson,l,mid-1,al,ar);
            if(ar>mid)insert(tr[rt].rson,mid+1,r,al,ar);
            pushup(rt);
        }
        void remove(int rt,int l,int r,int al,int ar){
            if(al>r||ar<l||al>ar)return ;int mid=tr[rt].mid;
            if(al<=l&&r<=ar){work(rt,-1);ans-=tr[rt].sum;return ;}
            if(al<=mid&&mid<=ar)tr[rt].sm--,ans-=tr[rt].sm;
            pushdown(rt);
            if(al<mid)remove(tr[rt].lson,l,mid-1,al,ar);
            if(ar>mid)remove(tr[rt].rson,mid+1,r,al,ar);
            pushup(rt);
        }
}T;
vector<int>P[MAXM],G[MAXM];
class SuffixAutomaton{
    public:
        node tr[MAXM];int tot,las,dep[MAXM],f[MAXM][20];
    public:
        void init(){las=++tot;}
        int extend(int x,int y){
            int p=las,np=las=++tot;tr[np].len=tr[p].len+1;tr[np].ed=y;
            for(;p&&!tr[p].ch[x];p=tr[p].fa)tr[p].ch[x]=np;
            if(!p){tr[np].fa=1;return np;}int q=tr[p].ch[x];
            if(tr[q].len==tr[p].len+1){tr[np].fa=q;return np;}
            int nq=++tot;tr[nq]=tr[q];tr[nq].len=tr[p].len+1;
            for(;p&&tr[p].ch[x]==q;p=tr[p].fa)tr[p].ch[x]=nq;
            tr[q].fa=tr[np].fa=nq;return np;
        }
        void dosaka(int u,int fa){
            dep[u]=dep[fa]+1;f[u][0]=fa;int siz=G[u].size();
            for(int i=1;i<20;i++)f[u][i]=f[f[u][i-1]][i-1];
            for(int i=0;i<siz;i++)dosaka(G[u][i],u);
        }
        int lca(int a,int b){
            if(dep[a]>dep[b])swap(a,b);
            for(int i=19;i>=0;i--)if(dep[f[b][i]]>=dep[a])b=f[b][i];
            if(a==b)return a;
            for(int i=19;i>=0;i--)if(f[a][i]^f[b][i])a=f[a][i],b=f[b][i];
            return f[a][0];
        }
        void sakura(){
            for(int i=1;i<=tot;i++)
                for(int j=0;j<4;j++)
                    if(tr[tr[i].ch[j]].len==tr[i].len+1)
                        P[i].pb(tr[i].ch[j]);
            for(int i=2;i<=tot;i++)G[tr[i].fa].pb(i);
            dosaka(1,0);
        }
}S;
void dosaka1(int u,int fa){
    father[u]=fa;sz[u]=1;int siz=P[u].size();
    for(int i=0;i<siz;i++){
        int v=P[u][i];dosaka1(v,u);sz[u]+=sz[v];
        if(sz[v]>sz[wson[u]])wson[u]=v;
    }
}
void dosaka2(int u,int tp){
    dfn[u]=++idx;dn[tp]=idx;ltp[u]=tp;
    pre[idx]=u;int siz=P[u].size();
    if(wson[u])dosaka2(wson[u],tp);
    for(int i=0;i<siz;i++){
        int v=P[u][i];if(v==wson[u])continue;
        dosaka2(v,v);
    }
    if(u==tp)T.build(root[u],dfn[u],dn[u]);
}
int main(){
    //freopen("elixir.in","r",stdin);
    //freopen("elixir.out","w",stdout);
    scanf("%s",str+1);read(Q);n=(int)strlen(str+1);
    S.init();for(int i=1;i<=n;i++)ip[i]=S.extend(str[i]-'a',i);S.sakura();
    dosaka1(1,0);int siz=P[1].size();
    for(int i=0;i<siz;i++)dosaka2(P[1][i],P[1][i]);
    for(int i=1;i<=Q;i++){
        int x,y;read(x);read(y);int z=S.lca(ip[x],ip[y]),w;
        if(z==1){answer[i]=ans;continue;}
        if(!vis[z]){vis[z]=1;while(z>1)w=ltp[z],T.insert(root[w],dfn[w],dn[w],dfn[w],dfn[z]),z=father[w];}
        else{vis[z]=0;while(z>1)w=ltp[z],T.remove(root[w],dfn[w],dn[w],dfn[w],dfn[z]),z=father[w];}
        answer[i]=ans;
    }
    for(int i=Q;i>0;i--)print(answer[i]);
    fwrite(opt,1,obuf+(1<<22)-opt,stdout);
    return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值