BZOJ3879:SvT(后缀自动机+虚树)

日更两篇,真刺激。

题目
题意:给出一个串,若干个询问。每个询问给出一些后缀,问它们两两之间LCP的长度和。

后缀数组的写法太简单显然了,把后缀按顺序拿出来,只用一个单调栈就可以了,但我并不会…所以就是SAM了。

经过一轮求后缀LCP模型在SAM上乱套,发现就是反串建SAM,得到后缀树。后缀树上结点的LCA的长度就是LCP长度。对于这里,把询问给出的结点在后缀树上建出虚树,每个点算贡献即可。
具体是这样的,对于x的两个儿子son1和son2, siz[son1]siz[son2]dep[x] 就能贡献答案。

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))

typedef long long LL;

const int N=1001000;
const LL mo=23333333333333333;

void read(int &hy)
{
    hy=0;
    char cc=getchar();
    while(cc<'0'||cc>'9')
    cc=getchar();
    while(cc>='0'&&cc<='9')
    {
        hy=(hy<<3)+(hy<<1)+cc-'0';
        cc=getchar();
    }
}

int n,m,k,a[N];
int son[N][26],pre[N],dep[N],w[N],cnt=1,last=1;
int head[N],nex[N],to[N],cur;
int Chtholly[N],top[N],len[N],tim[N],siz[N],times;
int st[N];
char cc[N];
LL ans;
bool vis[N];

void Insert(int x,int num)
{
    dep[++cnt]=dep[last]+1;
    w[num]=cnt;
    int np=cnt,p=last;
    last=cnt;
    for(;!son[p][x];p=pre[p])
    son[p][x]=np;
    if(!p)
    pre[np]=1;
    else
    {
        int q=son[p][x];
        if(dep[q]==dep[p]+1)
        pre[np]=q;
        else
        {
            dep[++cnt]=dep[p]+1;
            int nq=cnt;
            pre[nq]=pre[q];
            pre[q]=pre[np]=nq;
            mmcp(son[nq],son[q]);
            for(;son[p][x]==q;p=pre[p])
            son[p][x]=nq;
        }
    }
}

void add(int u,int v)
{
    to[++cur]=v;
    nex[cur]=head[u];
    head[u]=cur;
}

void dfs(int x)
{
    siz[x]=1;
    tim[x]=++times;
    for(int h=head[x];h;h=nex[h])
    {
        len[to[h]]=len[x]+1;
        dfs(to[h]);
        siz[x]+=siz[to[h]];
        if(siz[to[h]]>siz[Chtholly[x]])
        Chtholly[x]=to[h];
    }
}

void dfs2(int x,int tp)
{
    top[x]=tp;
    if(Chtholly[x])
    dfs2(Chtholly[x],tp);
    for(int h=head[x];h;h=nex[h])
    if(to[h]!=Chtholly[x])
    dfs2(to[h],to[h]);
}

int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(len[top[x]]<len[top[y]])
        swap(x,y);
        x=pre[top[x]];
    }
    return len[x]<len[y] ? x : y;
}

bool cmp(int x,int y)
{
    return tim[x]<tim[y];
}

void dp(int x)
{
    siz[x]=vis[x] ? 1 : 0;
    for(int h=head[x];h;h=nex[h])
    {
        dp(to[h]);
        ans+=(LL)siz[to[h]]*siz[x]*dep[x];
        siz[x]+=siz[to[h]];
    }
    head[x]=0;
}

void work()
{
    ans=0;
    k=cur=0;
    int kk;
    read(kk);
    for(int i=1;i<=kk;i++)
    {
        int by;
        read(by);
        if(!vis[w[by]])
        a[++k]=w[by];
        vis[w[by]]=1;
    }
    sort(a+1,a+k+1,cmp);

    int t=0;
    for(int i=1;i<=k;i++)
    {
        if(!t)
        {
            st[++t]=a[i];
            continue;
        }
        int lc=lca(a[i],st[t]);
        while(lc!=st[t])
        {
            if(tim[lc]>=tim[st[t-1]])
            {
                add(lc,st[t--]);
                if(lc!=st[t])
                st[++t]=lc;
                break;
            }
            else
            add(st[t-1],st[t]),t--;
        }
        st[++t]=a[i];
    }

    while(t!=1)
    add(st[t-1],st[t]),t--;

    dp(st[1]);
    for(int i=1;i<=k;i++)
    vis[a[i]]=0;
    printf("%lld\n",ans);
}

int main()
{
    cin>>n>>m;
    scanf("%s",cc);
    for(int i=n;i>=1;i--)
    Insert(cc[i-1]-'a',i);

    for(int i=2;i<=cnt;i++)
    add(pre[i],i);

    dfs(1);
    dfs2(1,1);
    mmst(head,0);

    while(m--)
    work();

    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值