bzoj 2754 [SCOI2012]喵星球上的点名 后缀数组+莫队

本文介绍了一种使用后缀数组和莫队算法解决字符串查询问题的方法。通过构建后缀数组并应用莫队算法来高效处理多个查询,求解不同数目的子串区间问题。

先把所有串按顺序放到一起,两个串间加非法字符隔开,求一个后缀数组。

然后对于询问,满足条件的子串在后缀数组上一定是连续一段区间。
这个区间的左右端点可以在读入的过程中二分求。

然后这个问题变成了多组询问求一段区间内不同的数的个数。
莫队裸题。
慢着,每个元素的出现次数怎么求呀?
莫队时维护,设共有cnt个询问,那么第i个询问时一个元素的出现状态的改变会影响到cnt-i+1个询问中该元素的出现状态。因此答案累加相应次数就行了。

#include <bits/stdc++.h>
using namespace std;
#define N 210000
#define A 11000
int n,m,len,cnt,sz;
int a[N],bel[N],sa[N],has[N],tr[N],rank[N],ans[N];
int num[N],sum,a1[N];
int cmp(int x,int y,int k)
{
    if(x+k>len||y+k>len)return 0;
    return rank[x]==rank[y]&&rank[x+k]==rank[y+k];
}
void getsa()
{
    int i,cnt;
    for(i=1;i<=len;i++)has[a[i]]++;
    for(i=0,cnt=0;i<=A;i++)if(has[i])tr[i]=++cnt;
    for(i=1;i<=A;i++)has[i]+=has[i-1];
    for(i=1;i<=len;i++)rank[i]=tr[a[i]],sa[has[a[i]]--]=i;
    for(int k=1;cnt!=len;k<<=1)
    {
        for(i=1;i<=len;i++)has[i]=0;
        for(i=1;i<=len;i++)has[rank[i]]++;
        for(i=1;i<=len;i++)has[i]+=has[i-1];
        for(i=len;i>=1;i--)if(sa[i]>k)tr[sa[i]-k]=has[rank[sa[i]-k]]--;
        for(i=1;i<=k;i++)tr[len-i+1]=has[rank[len-i+1]]--;
        for(i=1;i<=len;i++)sa[tr[i]]=i;
        for(i=1,cnt=0;i<=len;i++)tr[sa[i]]=cmp(sa[i],sa[i-1],k) ? cnt:++cnt;
        for(i=1;i<=len;i++)rank[i]=tr[i];
    }
}
struct node
{
    int l,r,pos;
    node(){}
    node(int l,int r,int pos):l(l),r(r),pos(pos){}
    friend bool operator < (const node &r1,const node &r2)
    {
        if(r1.l/sz==r2.l/sz)return r1.r<r2.r;
        return r1.l/sz<r2.l/sz;
    };
}p[N];
void insert(int x,int tp,int now)
{
    int t=bel[sa[x]];
    num[t]+=tp;
    if(tp==1&&num[t]==1)sum++,a1[t]+=(cnt-now+1);
    if(tp==-1&&num[t]==0)sum--,a1[t]-=(cnt-now+1);
}
int main()
{
    //freopen("tt.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1,l;i<=n;i++)
        for(int j=0;j<=1;j++)
        {
            scanf("%d",&l);
            for(int k=1;k<=l;k++)
                scanf("%d",&a[++len]),bel[len]=i;
            a[++len]=A;
        }
    sz=sqrt(len)+1;
    getsa();
    for(int n1,now=1;now<=m;now++)
    {
        scanf("%d",&n1);
        int ln=1,rn=len;
        for(int i=1,x;i<=n1;i++)
        {
            scanf("%d",&x);
            int l=ln,r=rn,lt;
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if(a[sa[mid]+i-1]<x)l=mid+1;
                else r=mid-1;
            }
            lt=l;l=ln,r=rn;
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if(a[sa[mid]+i-1]<=x)l=mid+1;
                else r=mid-1;
            }
            rn=r;ln=lt;
        }
        if(ln<=rn)p[++cnt]=node(ln,rn,now);
    }
    sort(p+1,p+1+cnt);
    for(int i=1,l=1,r=0;i<=cnt;i++)
    {
        while(l<p[i].l)insert(l,-1,i),l++;
        while(l>p[i].l)l--,insert(l,1,i);
        while(r<p[i].r)r++,insert(r,1,i);
        while(r>p[i].r)insert(r,-1,i),r--;
        ans[p[i].pos]=sum;
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
    for(int i=1;i<=n;i++)
        printf("%d%c",a1[i],i==n ? '\n':' ');
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值