【题解】CF1037H Security
\(\text{Solution:}\)
代码中给出了封装好的线段树和后缀自动机。
考虑没有 \(L,R\) 限制怎么做。
显然直接在上面跑匹配:先按照 \(T\) 来跑,如果中间只能找到比他大的,就直接输出;然后是两种情况:一种是将 \(T\) 完整匹配完了,另一种是我们依靠走最小字符的串后面匹配不上了。
先考虑匹配完的情况:首先如果匹配完了,在后面如果可以接上一个最小的字符那就是答案了。先把这种情况判断掉。
那接下来上述两种情况的处理方式是一样的:考虑记录从根转移到当前失配或者是匹配结束的点的路径,考虑从后往前一个个更新是否能到达一个字典序更大的点。因为从后往前一定可以保证字典序最小。
如果找不到那就是无解,输出 \(-1.\)
那带上了限制怎么做?
那无非是在匹配的时候需要判断能不能走这个点。设当前走到这个点已经匹配的长度为 \(len,\) 则如果可以匹配,就需要满足这个点代表的 endpos 在区间 \([l+len-1,r]\) 中出现过。
那下面的问题就是如何维护每一个点的 endpos 集合了。考虑 parent 树的性质:节点的 endpos 等于其孩子的 endpos 集合之并。而后缀对应的节点的 endpos 就是后缀的位置。
于是我们可以考虑在 parent 树上进行线段树合并,维护出每一个点的 endpos 集合。注意每一个点都要有一棵自己的树。否则结构是错的。
那么注意空间问题: SAM 的空间最大达到 \(2n\) 级别,本题就是 \(2\times 10^5,\) 对应线段树合并空间约为 \(n\log n,\) 即 \(3.6\times 10^6\) 左右。
所以一定要算好空间。
其他的细节就是计算区间和情况特判了。
动态开点的线段树不需要维护什么东西,由于我们的询问属于存在性询问,直接询问那个区间是不是有建立好的点即可。
复杂度 \(O(\sum |T|\log |S|)\)
#include<bits/stdc++.h>
using namespace std;
const int N=4e6+10;
const int SN=3e5+10;
int n,slen,Alen;
namespace SGT{
int ls[N],rs[N],node;
void change(int &x,const int &L,const int &R,const int &pos){
if(!x)x=++node;
if(L==R){return;}
int mid=(L+R)>>1;
if(pos<=mid)change(ls[x],L,mid,pos);
else change(rs[x],mid+1,R,pos);
}
bool query(const int &x,const int &L,const int &R,const int &ql,const int &qr){
if(!x)return 0;
if(L>=ql&&R<=qr)return 1;
int mid=(L+R)>>1;
int res=0;
if(ql<=mid)res=query(ls[x],L,mid,ql,qr);
if(mid<qr)res|=query(rs[x],mid+1,R,ql,qr);
return res;
}
int merge(const int &x,const int &y){
if(!x||!y)return x|y;
int p=++node;
ls[p]=merge(ls[x],ls[y]);
rs[p]=merge(rs[x],rs[y]);
return p;
}
}
using namespace SGT;
namespace SAM{
int len[SN],pa[SN],rt[SN],tot=1,last=1,ch[SN][26],edp[SN];
vector<int>G[SN];
void insert(const int &c){
int p=last;
int np=++tot;
last=tot;len[np]=len[p]+1;
for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
if(!p)pa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1)pa[np]=q;
else{
int nq=++tot;
len[nq]=len[p]+1;
pa[nq]=pa[q];pa[q]=pa[np]=nq;
memcpy(ch[nq],ch[q],sizeof ch[q]);
for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
}
}
}
void dfs(int x){
if(edp[x]){
change(rt[x],1,Alen,edp[x]);
}
for(auto v:G[x]){
dfs(v);
rt[x]=merge(rt[x],rt[v]);
}
}
void BuildTree(){
for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
dfs(1);
}
}
using namespace SAM;
char s[SN];
inline void print(vector<char> A){
for(auto i:A)putchar(i);
puts("");
}
char check(int now,char T,int len,int l,int r){
int v=T-'a';
for(int i=v+1;i<26;++i){
if(ch[now][i]&&query(rt[ch[now][i]],1,Alen,l+len-1,r))return i+'a';
}
return '#';
}
void Mate(int l,int r){
int now=1;
vector<int>Path;
vector<char>Ans;
Path.push_back(1);
int len=0;
int Tag=0;
for(int i=1;i<=slen;++i){
int pos,ck=0;
for(pos=s[i]-'a';pos<26;++pos){
if(ch[now][pos]&&query(rt[ch[now][pos]],1,Alen,l+len,r)){ck=1;break;}
}
if(!ck){Tag=1;break;}
Ans.push_back(pos+'a');
if(pos!=s[i]-'a'){
print(Ans);
return;
}
Path.push_back(ch[now][pos]);
len++;
now=ch[now][pos];
}
if(Tag){
// puts("*******");
int plen=(int)Path.size();
for(int i=plen-2;~i;--i){
char lat=check(Path[i],s[len-plen+1+i+1],len-plen+1+i+1,l,r);
Ans.pop_back();
if(lat=='#')continue;
else {Ans.push_back(lat);print(Ans);return;}
}
puts("-1");
return;
}
for(int nxtpos=0;nxtpos<26;++nxtpos){
if(ch[now][nxtpos]&&query(rt[ch[now][nxtpos]],1,Alen,l+len,r)){
Ans.push_back(nxtpos+'a');
print(Ans);
return;
}
}
int plen=(int)Path.size();
for(int i=plen-2;~i;--i){
char lat=check(Path[i],s[slen-plen+1+i+1],slen-plen+1+i+1,l,r);
Ans.pop_back();
if(lat=='#')continue;
else {Ans.push_back(lat);print(Ans);return;}
}
puts("-1");
return;
}
int main(){
scanf("%s",s+1);
slen=strlen(s+1);Alen=slen;
for(int i=1;i<=slen;++i)insert(s[i]-'a');
// puts("insert");
for(int i=1,now=1;i<=slen;++i){
int v=s[i]-'a';
now=ch[now][v];
edp[now]=i;
}
// puts("change");
BuildTree();
// puts("BuildTree");
scanf("%d",&n);
while(n--){
int L,R;
scanf("%d%d",&L,&R);
scanf("%s",s+1);
slen=strlen(s+1);
Mate(L,R);
}
return 0;
}