题解字母 (letter)
Posted xing-ling
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解字母 (letter)相关的知识,希望对你有一定的参考价值。
【题解】字母 (letter)
没有传送门。
【题目描述】
给定一个长为 (n) 字符串 ( ext{S}),现取出 ( ext{S}) 的所有子串,并按字典序从小到大排序,然后将这些排好序的字符串首尾相接,记为字符串 ( ext{T})。共有 ( ext{Q}) 次询问,每次询问 ( ext{T}) 中的第 ( ext{K}) 个字符。
操作强制在线,每次询问给出两个正整数 ( ext{P,M}),设 ( ext{G}) 为之前所有询问答案的 ( ext{ASCII}) 码之和(初始为 (0)),则对于该次询问:( ext{K}=( ext{P} imes ext{G}) mod ext{M}) 。
【输入格式】
第一行输入一个字符串 ( ext{S}),第二行一个正整数 ( ext{Q}) 。
接下来 ( ext{Q}) 行,每行两个正整数 ( ext{P,M}) 。
【输出格式】
对于每次询问,输出一行一个字符表示答案。
【输入输出样例】
样例输入:
caaacacbbbababba
6
3 101
16 101
21 41
22 129
33 131
40 100
样例输出:
a
a
a
b
c
a
【数据范围】
(100\%:) (1 leqslant ext{M} leqslant | ext{T}|, 1 leqslant ext{P} leqslant 10^9),( ext{S}) 中仅包含小写字母。
测试点 | 字符串 ( ext{S}) 的长度 (n) | 询问的次数 ( ext{Q}) |
---|---|---|
(1) | (1 leqslant n leqslant 50) | (1 leqslant ext{Q} leqslant 200000) |
(2,3) | (1 leqslant n leqslant 2000) | (1 leqslant ext{Q} leqslant 20000) |
(4,5) | (1 leqslant n leqslant 200000) | (1 leqslant ext{Q} leqslant 10) |
(6,10) | (1 leqslant n leqslant 200000) | (1 leqslant ext{Q} leqslant 200000) |
【分析】
鬼畜题见多了后遇到这种码农题感觉开心的要死。
看到有 “所有子串” 这样的字样,基本上就是三种后缀算法实锤了,(std) 给的是后缀数组的做法,但个人认为这道题用后缀树要简单一些。
首先是对所有的子串进行排序,明显为后缀树的板子操作。用 ( ext{SAM}) 构造好 ( ext{Parent}) 树,顺序遍历得到的节点序列 (id) 即可满足字典序从小到大,且对于同一节点中保存的子串,其长度越小字典序就越小。
在建 ( ext{SAM}) 的时候记录下 (maxlen,siz) ((|{endpos}|)) 和 (pos) (({endpos}) 中的任意一个 ()),则 ( ext{SAM}) 中的一个保存信息的节点 (x) 所囊括的子串有:某一个前缀 (prefix) 的 (len) 个后缀,长度分别为 (minlen(x),minlen(x)+1...maxlen(x)),其中 (len=maxlen(x)-minlen(x)+1)。所以 (x) 中所囊括的字符个数为:(cnt[x]=siz[x]sum_{i=minlen(x)}^{maxlen(x)}i) 。
设 (S[n]=sum_{i=1}^{n}cnt[id[i]]),每次查询在 (S) 中二分找到答案所在的节点 (x),然后在 ([minlen(x),maxlen(x)]) 中二分找到答案所在的子串长度,最后用 ( ext{K}) 对该子串长度取模即可得到答案的位置(取模是因为该子串可能会出现多次)。
后缀算法的细节考虑通常都很复杂,这道题也不例外,思维难度不高,但实现起来非常麻烦,考试时码了一个小时 ( ext{code}),然后又 ( ext{debug}) 半小时,算上写暴力和对拍的时间,前前后后花了接近三个小时。。。
时间复杂度为:(O(n+qlogn)),实测比后缀数组的 (O((n+q)logn)) 快了好几倍。
【Code】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=4e5+10;
int n,x,y,T;LL P,M,G,K;char s[N>>1];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct Sakura2{
int O,last,pos[N],siz[N],link[N],maxlen[N],trans[N][26];
//siz[x]: 节点x的出现次数 (endpos的大小)
//link[x]: x在parent树上的父亲
//pos[x]: 节点x某一次出现位置 (用于构建后缀树中的压缩边link[x]->x),
///翻转后的s中 倒序遍历pos[x]-maxlen[link[x]] -> pos[x]-maxlen[x]+1得到的字符串 即为后缀树中的压缩边 link[x]->x
inline void insert(Re ch,Re id){//SAM新建节点
Re z=++O,p=last;pos[z]=id,siz[z]=1,maxlen[z]=maxlen[last]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
Re x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;
else{
Re y=++O;maxlen[y]=maxlen[p]+1,pos[y]=pos[x];
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[z]=link[x]=y;
}
}
last=z;
}
int t,ID[N],to[N][26];LL S[N];
//to[x][ch]: 后缀树的trans数组
//ID[x]: 顺序遍历后缀树的第x个节点编号
//S[x]: 顺序遍历后缀树的前x个节点总共代表了多少个字符
inline void dfs1(Re x){//遍历 SAM的parent树后缀树 获取siz
for(Re i=0;i<26;++i)
if(to[x][i])dfs1(to[x][i]),siz[x]+=siz[to[x][i]];
}
inline void dfs2(Re x){//遍历 SAM的parent树后缀树 获取节点顺序
if(x!=1)ID[++t]=x;//没有储存信息的根节点1就不要了
for(Re i=0;i<26;++i)if(to[x][i])dfs2(to[x][i]);
}
inline LL calc(Re L,Re R){return 1ll*(L+R)*(R-L+1)/2;}//计算从L加到R的等差数列
inline void build(){
last=O=1;//根节点设为1
for(Re i=1;i<=n/2;++i)swap(s[i],s[n-i+1]);//翻转原字符串
for(Re i=1;i<=n;++i)insert(s[i]-'a',i);
for(Re i=2;i<=O;++i)to[link[i]][s[pos[i]-maxlen[link[i]]]-'a']=i;//构建后缀树。
//由于pos[x]是endpos[x]中的任意一个,所以获取边link[x]->x压缩掉的字符串信息时只能用pos[x],不能用pos[link[x]]
dfs1(1),dfs2(1);//遍历 SAM的parent树后缀树
for(Re i=1;i<=t;++i)x=ID[i],S[i]=S[i-1]+1ll*siz[x]*calc(maxlen[link[x]]+1,maxlen[x]);
}
inline char ask(LL K){
Re l=1,r=t,x;
while(l<r){//二分找到第一个大于等于K的位置
Re mid=l+r>>1;
if(S[mid]<K)l=mid+1;
else r=mid;
}
x=ID[l],K-=S[l-1],l=maxlen[link[x]]+1,r=maxlen[x];
while(l<r){//二分找到第一个大于等于K的位置
Re mid=l+r>>1;
if(1ll*siz[x]*calc(maxlen[link[x]]+1,mid)<K)l=mid+1;
else r=mid;
}
K-=1ll*siz[x]*calc(maxlen[link[x]]+1,l-1),K=(K-1)%l+1;//注意取模的方式
return s[pos[x]-K+1];//注意方向:往左数第K位
}
char ans;
inline void sakura(){
build();
while(T--)scanf("%lld%lld",&P,&M),K=P*G%M+1,G+=(ans=ask(K)),putchar(ans),puts("");
}
}T2;
int main(){
freopen("letter.in","r",stdin);
freopen("letter.out","w",stdout);
scanf("%s",s+1),n=strlen(s+1);in(T);
T2.sakura();
fclose(stdin);
fclose(stdout);
}
以上是关于题解字母 (letter)的主要内容,如果未能解决你的问题,请参考以下文章
bzoj2789[Poi2012]Letters 树状数组求逆序对