BZOJ3879:SvT (后缀数组+单调栈)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3879


题目分析:sro popoqqq

其实这题可以用SAM+虚树来做,而且我虚树也写得不熟。但SA+单调栈实在是太方便了,就懒得再写了QAQ。

上次写SA是NOIP前的事了,差点想不起怎么写,一开始构造Height数组还出错了……其实后缀数组的原理我早忘得差不多了,只是去年省赛前集训连切27题SA,所以板子还能背出来。

这题的模数是假的,算一下你就会发现答案不爆long long,最后再模就好了


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000100;
const int maxl=22;
const long long M=23333333333333333LL;
typedef long long LL;

int cnt[maxn];
int temp1[maxn];
int temp2[maxn];
int *X=temp1,*Y=temp2;

int ST[maxn][maxl];
int sa[maxn];
int Height[maxn];
int m;

int Node[maxn];
int lcp[maxn];
int num[maxn];
int tail;
LL sum;

int Lg[maxn];
int a[maxn];
char s[maxn];
int n,q;
LL ans;

bool Comp(int p,int q,int len)
{
    return ( Y[p]==Y[q] && Y[p+len]==Y[q+len] );
}

void Da()
{
    m=30;
    for (int i=0; i<m; i++) cnt[i]=0;
    for (int i=0; i<n; i++) cnt[ X[i]=a[i] ]++;
    for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
    for (int i=n-1; i>=0; i--) sa[ --cnt[ X[i] ] ]=i;

    int p=1;
    for (int j=1; p<n; j<<=1,m=p)
    {
        p=0;
        for (int i=n-j; i<n; i++) Y[p++]=i;
        for (int i=0; i<n; i++) if (sa[i]>=j) Y[p++]=sa[i]-j;

        for (int i=0; i<m; i++) cnt[i]=0;
        for (int i=0; i<n; i++) cnt[ X[ Y[i] ] ]++;
        for (int i=1; i<m; i++) cnt[i]+=cnt[i-1];
        for (int i=n-1; i>=0; i--) sa[ --cnt[ X[ Y[i] ] ] ]=Y[i];

        p=1;
        swap(X,Y);
        X[ sa[0] ]=0;
        for (int i=1; i<n; i++) X[ sa[i] ]=Comp(sa[i-1],sa[i],j)? p-1:p++;
    }
}

void Calc_height()
{
    Height[0]=0;
    int k=0;
    for (int i=0; i<n; i++)
    {
        if (k) k--;
        if (!X[i]) continue;
        int j=sa[ X[i]-1 ];
        while (a[i+k]==a[j+k]) k++;
        Height[ X[i] ]=k;
    }
}

bool Comp1(int x,int y)
{
    return X[x]<X[y];
}

int Lcp(int x,int y)
{
    x=X[x];
    y=X[y];
    int z=Lg[y-x];
    y-=(1<<z);
    return min(ST[x][z],ST[y][z]);
}

int main()
{
    //freopen("3879.in","r",stdin);
    //freopen("3879.out","w",stdout);

    scanf("%d%d",&n,&q);
    scanf("%s",&s);
    for (int i=0; i<n; i++) a[i]=s[i]-'a'+1;
    a[n++]=0;

    Da();
    Calc_height();

    Lg[1]=0;
    for (int i=2; i<=n; i++) Lg[i]=Lg[i>>1]+1;
    for (int i=0; i<n; i++) ST[i][0]=Height[i+1];
    for (int j=1; j<maxl; j++)
        for (int i=0; i<n; i++)
        {
            int mid=min(i+(1<<(j-1)),n-1);
            ST[i][j]=min(ST[i][j-1],ST[mid][j-1]);
        }

    while (q--)
    {
        scanf("%d",&m);
        for (int i=1; i<=m; i++) scanf("%d",&Node[i]),Node[i]--;

        sort(Node+1,Node+m+1,Comp1);
        tail=1;
        sum=lcp[1]=n-Node[1];
        num[1]=1;
        ans=0;

        for (int i=2; i<=m; i++)
            if (Node[i-1]!=Node[i])
            {
                int x=Lcp(Node[i-1],Node[i]);
                int Num=0;
                while (lcp[tail]>x) sum-=( num[tail]*(lcp[tail]-x) ),Num+=num[tail--];
                lcp[++tail]=x;
                num[tail]=Num;
                ans+=sum;
                lcp[++tail]=n-Node[i];
                sum+=lcp[tail];
                num[tail]=1;
            }

        ans%=M;
        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值