洛谷 P6257 [ICPC2019 WF]First of Her Name【后缀数组】


题目:

传送门


题意:

n n n个人的名字构造形式如一棵字典树,给定一个前缀 s s s,问有多少个人的名字的前缀是 s s s
共有 q q q组询问


分析:

因为前缀是我们所陌生的,但后缀我们熟呀,就把整个树倒过来,就变成求后缀了
问后缀为 s s s的有多少个,如果我们将每个名字都标号并且使得其有序的话,就变成要求出编号最小 ( l ) (l) (l)的后缀为 s s s的哪个,以及最大的 ( r ) (r) (r),最后答案就是 r − l + 1 r-l+1 rl+1
基于这样的思路,我们考虑将树上的名字也就是字符串排序,后缀数组能在 O ( n l o g n ) O(nlogn) O(nlogn)的时间轻松解决
l , r l,r l,r则用二分来确定位置


代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>  
#include<string>
#include<algorithm>
#include<queue>
#define LL long long
using namespace std;
inline LL read() {
    LL d=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
    return d*f;
}
int n,m=30,q,cnt;
int sa[1000005],sa2[1000005],rk[1000005],rk2[1000005],tp[1000005],c[1000005];
int fa[1000005][21],ch[1000005],z[1000005],dep[1000005];
void Rsort(int *rk,int *sa)
{
    for(int i=0;i<=m;i++) c[i]=0;
    for(int i=1;i<=n;i++) c[rk[tp[i]]]++;
    for(int i=1;i<=m;i++) c[i]+=c[i-1];
    for(int i=n;i>=1;i--) sa[c[rk[tp[i]]]--]=tp[i];
    return;
}
void SA()
{
    for(int i=1;i<=n;i++) rk[i]=ch[i],tp[i]=i;
    Rsort(rk,sa);
    for(int k=1,w=0;k<n;k<<=1,w++)
	{
        int j=0;
        for(int i=1;i<=n;i++) rk2[i]=rk[fa[i][w]];
        for(int i=1;i<=n;i++) tp[i]=i;
        Rsort(rk2,sa2);
        for(int i=1;i<=n;i++) tp[i]=sa2[i];
        Rsort(rk,sa);
		swap(rk,tp);rk[sa[1]]=j=1;
        for(int i=2;i<=n;i++)
    	  rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[fa[sa[i]][w]]==tp[fa[sa[i-1]][w]]?j:++j);
        m=j;
    }
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
    return;
}
int check(int x)
{
    int len=min(cnt,dep[sa[x]]),k=sa[x];
    for(int i=1;i<=len;i++)
	{
        if(ch[k]<z[i]) return -1;
        if(ch[k]>z[i]) return 1;
        k=fa[k][0];
    }
    if(len==cnt) return 0;
    return -1;
}
void solve(int len)
{
    int L=n+1,R=0,l=1,r=n;
    while(l<=r)
	{
        int mid=(l+r)>>1;
        if(check(mid)>=0) L=mid,r=mid-1;
		else l=mid+1;
    }    
    l=1,r=n;
    while(l<=r)
	{
        int mid=(l+r)>>1;
        if(check(mid)<=0) R=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",R-L+1);
    return;
}
int main()
{
	n=read();q=read();
	dep[1]=1;
    for(int i=1;i<=n;i++)
	{
        char c=getchar(); 
        while(c<'A'||c>'Z') c=getchar();
		int x=read();
        ch[i]=c-'A'+1;
        fa[i][0]=x;dep[i]=dep[x]+1;
        for(int j=1;j<21;j++) fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    SA();
    while(q--)
	{
        char c=getchar();
        while(c<'A'||c>'Z') c=getchar();
        cnt=0;
        while(c>='A'&&c<='Z') z[++cnt]=c-'A'+1,c=getchar();
        solve(cnt);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值