peiwenjun's blog 没有知识的荒原

P2336 [SCOI2012]喵星球上的点名 题解

题目描述

\(n\) 个喵星人,每个人的名字由两个字符串构成。

\(m\) 个点名串,如果这个串是某个喵星人的姓或名的子串,那么这个喵星人必须答到。

对每个点名串,求有多少喵星人答到。

对每个喵星人,求它答到多少次。

数据范围

  • \(1\le n\le 5\cdot 10^4,1\le m\le 10^5\) ,保证名字长度和与点名串长度和分别 \(\le 10^5\) ,字符集大小为 \(10^4\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{125MB}\)

分析

对每个喵星人,将姓和名拼接起来,中间插入一个不存在的字符,那么当且仅当点名串(记为 \(t_j\) )是这个新字符串(记为 \(s_i\) )的子串时才会答到。

将所有 \(s_i\)\(t_j\) 拼接起来,中间插入一个不存在的字符,并建立后缀数组。

\(s_i\) 的所有字符染成第 \(i\) 种颜色,注意到子串等价于后缀的前缀。

对于每个 \(t_j\) ,在 sa[] 上二分找到满足 \(\texttt{lcp}\ge|t_j|\) 的左右端点,那么区间内所有颜色对应的喵星人都要答到。

于是第一问变成了区间数颜色,记录上一次出现相同颜色的位置 \(pre_i\) ,离线后在位置 \(pre_i\) 删除贡献,在位置 \(i\) 加入贡献,询问就是求后缀和。

对于第二问,我们要求每种颜色与多少个区间有交。

考虑增量计算贡献,即求有多少区间与 \(i\) 有交,并且与 \(pre_i\) 无交。

离线扫描线维护所有与当前位置 \(i\) 有交的区间,在扫描到区间的左右端点时更新。

那么区间 \([l,r]\)\(pre_i\) 无交等价于 \(l\gt pre_i\) ,用树状数组维护所有的 \(l\) ,询问依然是后缀和。

时间复杂度 \(\mathcal O((n+m)\log n)\)

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int v=1e4+5,maxn=4e5+5;
int l,m,n;
int c[maxn],x[maxn],y[2*maxn];
int h[maxn],rk[maxn],sa[maxn];
int lg[maxn],f[19][maxn];
int s[maxn],col[maxn];
int lst[maxn],pre[maxn],res[maxn];
pii p[maxn],q[maxn];
vector<pii> v1[maxn],v2[maxn];
void read()
{
    static int k=0;
    scanf("%d",&k);
    while(k--) scanf("%d",&s[++l]);
    s[++l]=v;
}
void get_sa(int n)
{
    int m=v;
    for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
    for(int i=1;i<=m;i++) c[i]+=c[i-1];
    for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(int i=n-k+1;i<=n;i++) y[++num]=i;
        for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++) c[i]=0;
        for(int i=1;i<=n;i++) c[x[i]]++;
        for(int i=1;i<=m;i++) c[i]+=c[i-1];
        for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
        for(int i=1;i<=n;i++) y[i]=x[i],x[i]=0;
        x[sa[1]]=num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
        if(num==n) break;
        m=num;
    }
}
void get_height(int n)
{
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
    s[0]=-1;
    for(int i=1,k=0;i<=n;i++)
    {
        if(k) k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
        h[rk[i]]=k;
    }
}
inline int lcp(int l,int r)
{
    int k=lg[r-l+1];
    return min(f[k][l],f[k][r-(1<<k)+1]);
}
inline void add(int x,int v)
{
    while(x) c[x]+=v,x-=x&(-x);
}
inline int query(int x)
{
    int res=0;
    while(x<=l) res+=c[x],x+=x&(-x);
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) p[i].fi=l+1,read(),read(),p[i].se=l-1;
    for(int i=1;i<=m;i++) q[i].fi=l+1,read(),q[i].se=l-1;
    get_sa(l),get_height(l);
    for(int i=2;i<=l;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=l;i++) f[0][i]=h[i];
    for(int j=1;j<=18;j++)
        for(int i=1;i+(1<<j)-1<=l;i++)
            f[j][i]=min(f[j-1][i],f[j-1][i+(1<<(j-1))]);
    for(int i=1;i<=m;i++)
    {
        int len=q[i].se-q[i].fi+1,pos=rk[q[i].fi];
        auto get_l=[&]()
        {
            int l=0,r=pos;
            while(r-l>1)
            {
                int mid=(l+r)>>1;
                lcp(mid+1,pos)>=len?r=mid:l=mid;
            }
            return r;
        };
        auto get_r=[&]()
        {
            int l=pos,r=::l+1;
            while(r-l>1)
            {
                int mid=(l+r)>>1;
                lcp(pos+1,mid)>=len?l=mid:r=mid;
            }
            return l;
        };
        int l=get_l(),r=get_r();
        v1[r].push_back(mp(l,i));
        v2[l].push_back(mp(l,1)),v2[r+1].push_back(mp(l,-1));
    }
    for(int i=1;i<=n;i++) for(int j=p[i].fi;j<=p[i].se;j++) col[rk[j]]=i;
    for(int i=1;i<=l;i++) if(col[i]) pre[i]=lst[col[i]],lst[col[i]]=i;
    memset(c,0,sizeof(c));
    for(int i=1;i<=l;i++)
    {
        if(col[i]) add(pre[i],-1),add(i,1);
        for(auto p:v1[i]) res[p.se]=query(p.fi);
    }
    for(int i=1;i<=m;i++) printf("%d\n",res[i]);
    memset(c,0,sizeof(c));
    memset(res,0,sizeof(res));
    for(int i=1;i<=l;i++)
    {
        for(auto p:v2[i]) add(p.fi,p.se);
        if(col[i]) res[col[i]]+=query(pre[i]+1);
    }
    for(int i=1;i<=n;i++) printf("%d ",res[i]);
    putchar('\n');
    return 0;
}

posted on 2023-06-18 15:27  peiwenjun  阅读(6)  评论(0)    收藏  举报

导航