不看不知道,一看吓一跳,还以为是什么结构体。。。
上题解!
分析一下思路:
这段代码是用于解决类似 “喵星球点名” 问题的高效实现,核心通过后缀数组(Suffix Array) 处理字符串匹配,实现快速查询子串出现的次数及相关统计。以下是其原理分析:
一、问题背景
代码解决的问题可概括为:
有 n1 个 “喵星人”,每个喵星人有 “姓” 和 “名” 两个数字串。
有 m1 个查询串,每个查询需统计:有多少个喵星人的 “姓” 或 “名” 包含该查询串作为子串。
最终输出每个查询的结果,以及每个喵星人被匹配的总次数。
二、核心思路
将所有字符串(包括所有喵星人的姓、名,以及所有查询串)拼接成一个大字符串,通过后缀数组预处理,再利用后缀数组的特性快速定位查询串在原字符串中的位置,进而统计匹配的喵星人数量。
三、代码分步解析
1. 数据预处理:拼接字符串
目的:将所有相关字符串(姓、名、查询串)合并为一个大字符串,便于后续用后缀数组处理。
实现细节:
用 a[] 存储合并后的大字符串。
每个字符串(姓、名、查询串)之间用唯一分隔符(m 自增生成,确保与原字符不重复)隔开,避免不同字符串的后缀被误判为连续子串。
bl[] 数组标记每个位置属于哪个喵星人(姓和名属于同一喵星人)。
data[] 存储每个查询串在大字符串中的起始位置(st)和长度(len)。
2. 构建后缀数组(Suffix Array)
后缀数组是解决子串匹配问题的高效数据结构,核心包括 sa[] 和 rank[]:
sa[i]:表示排名第 i 的后缀的起始位置(即从 sa[i] 开始的后缀是所有后缀中字典序第 i 小的)。
rank[i]:表示起始位置为 i 的后缀的排名(即 rank[sa[i]] = i)。
代码通过倍增算法构建后缀数组:
初始化:按单个字符的大小排序,得到初始 rank[]。
倍增排序:每次按长度为 2^p 的前缀排序(p 不断倍增),通过两次基数排序(分别对后半段和前半段排序)高效更新 sa[] 和 rank[],直到所有后缀的排名唯一(k == n)。
3. 计算高度数组(Height Array)
height[i] 表示排名第 i 的后缀与排名第 i-1 的后缀的最长公共前缀(LCP)长度。
用途:通过 height[] 可以快速确定某一范围内的后缀是否包含相同的子串。
计算方法:利用 rank[] 的性质,通过递推公式高效计算(避免暴力比较):
4. 处理查询:统计匹配的喵星人
对于每个查询串,需确定其在大字符串中出现的位置,再统计对应的喵星人:
定位查询串范围:
查询串在大字符串中的起始位置为 data[i].st,其排名为 rank[data[i].st]。
利用 height[] 找到所有与查询串前缀相同的后缀:从 rank[data[i].st] 向左右扩展,直到 height 小于查询串长度 data[i].len,得到范围 [l, r]。
范围内的 sa[j](j ∈ [l, r])对应的后缀均以查询串为前缀,即包含该查询串作为子串。
统计匹配的喵星人:
遍历 [l, r] 中的 sa[j],通过 bl[sa[j]] 确定该位置属于哪个喵星人。
用 visit[] 标记已统计的喵星人(避免同一查询中重复计数),累加 tmp 得到本次查询的结果,并更新每个喵星人的总次数 ans[]。
四、效率分析
时间复杂度:
后缀数组构建:O(n log n)(n 为大字符串总长度)。
每个查询处理:O(r - l + n1)(r - l 为查询串匹配的后缀范围,n1 为喵星人数量)。
优势:相比暴力枚举子串(O(L^2) 预处理),后缀数组将子串匹配问题转化为范围查询,大幅提升了处理大量查询的效率,尤其适合字符串总长度较大的场景(如题目中总长 ≤ 1e5)。
总结
代码通过后缀数组 + 高度数组的经典组合,高效解决了 “多模式子串匹配” 问题。核心是利用后缀数组的排序特性和高度数组的前缀匹配特性,将查询串的匹配转化为范围查询,最终实现快速统计。这种方法在处理大量字符串和查询时,效率远高于暴力匹配。
上代码!
#include<cstdio>
#include<cstring>
#include<iostream>
#define N 2020010
inline int read(){
int x=0;char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch<='9'&&ch>='0') {x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m,a[N],n1,m1,bl[N],count[N],rank[N<<1],rank1[N],sa[N],height[N],k,tmp[N],visit[N],ans[N];
struct node{
int st,len;
}data[55000];
signed main(){
n1=read();m1=read();n=1;m=10000;
for (int i=1;i<=n1;++i){
int tmp=read();
for (int j=0;j<tmp;j) a[n+j]=read(),bl[n+j]=i;n+=tmp;a[n]=++m;
tmp=read();
for (int j=0;j<tmp;j) a[n+j]=read(),bl[n+j]=i;n+=tmp;a[n]=++m;
}int last=n-1;
for (int i=1;i<=m1;++i){
int tmp=read();
data[i].st=n;data[i].len=tmp;
for (int j=0;j<tmp;j) a[n+j]=read();n+=tmp;a[n]=++m;
}n-=1;
for (int i=1;i<=n;++i) count[a[i]]=1;
for (int i=1;i<=m;++i) count[i]+=count[i-1];
for (int i=1;i<=n;++i) rank[i]=count[a[i]];
k=0;count[0]=0;
for (int p=1;k!=n;p<<=1,m=k){
for (int i=1;i<=m;++i) count[i]=0;
for (int i=1;i<=n;i) count[rank[i+p]];
for (int i=1;i<=m;++i) count[i]+=count[i-1];
for (int i=n;i>=1;--i) tmp[count[rank[i+p]]--]=i;
for (int i=1;i<=m;++i) count[i]=0;
for (int i=1;i<=n;i) count[rank[i]];
for (int i=1;i<=m;++i) count[i]+=count[i-1];
for (int i=n;i>=1;--i) sa[count[rank[tmp[i]]]--]=tmp[i];
memcpy(rank1,rank,sizeof(rank)>>1);
rank[sa[1]]=k=1;
for (int i=2;i<=n;++i){
if (rank1[sa[i]]!=rank1[sa[i-1]]||rank1[sa[i]+p]!=rank1[sa[i-1]+p]) ++k;
rank[sa[i]]=k;
}
}
k=0;
for (int i=1;i<=n;++i){
if (rank[i]1) continue;
k=k0?0:k-1;
while (a[i+k]==a[sa[rank[i]-1]+k]) ++k;
height[rank[i]]=k;
}
for (int i=1;i<=m1;++i){
int l=rank[data[i].st],r=rank[data[i].st]+1;
while (height[l]>=data[i].len) --l;
while (height[r]>=data[i].len) r;--r;
int tmp=0;
for (int j=l;j<=r;j){
if (sa[j]>last) continue;
if (visit[bl[sa[j]]]==i) continue;
tmp;ans[bl[sa[j]]];visit[bl[sa[j]]]=i;
}
printf("%d\n",tmp);
}
for (int i=1;i<=n1;++i) printf("%d ",ans[i]);
return 0;
}
很抱歉,近期没有彩蛋
下期再见!uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu