CF1037H Security 题解

传送门
wind_whisper 好闪,拜谢 wind_whisper
要是洛谷有 CF 的 RMJ 就好了

题解

题意简述

给一个字符串 \(s\),每次询问给出 \(l\) \(r\) \(t\),查询一个 \(\large s_{l\dots r}\) 的子串,使得这个子串在满足字典序严格大于 \(t\) 的条件下字典序最小。

分析

显然这个问题可以使用 SAM 来解决。

考虑问题的弱化版本,即没有区间限制的求解。
\(s\) 建出 SAM,对每个询问,从左往右遍历 \(t\) 串。
设当前遍历到 \(t\) 的第 \(i\) 个字符,并且转移到了 SAM 的节点 \(x\)
如果 \(x\)\(t_i\) 这条转移边,那么沿着这条边转移,否则找到第一条大于 \(t_i\) 的转移边。
如果这条边存在,那么这就是所求字符串的最后一个字符,求解完毕,因为此时我们得到的字符串的字典序已经大于 \(t\)
如果这条边不存在,即 \(x\) 的所有转移边都小于 \(t_i\),或者 \(x\) 没有转移边,此时需要回溯到上一个节点继续求解。
这里有一种边界情况:当 \(t\) 的每一位都成功匹配时,直接沿着当前节点最小的转移边转移。这样 \(t\) 是得到的字符串的前缀,字典序小于它。


现在考虑原问题,因为添加了区间的限制,所以不能看到一条边就转移,需要考虑要转移到的节点有没有一个合法的 \(endpos\)
一个 \(endpos\) 是合法的,首先需要它在 \(r\) 的左边;其次,当我们转移到它的时候,我们的字符串会以它结尾,这个字符串的左端点需要在 \(l\) 的右边。
由于我们需要用到 \(endpos\) 的具体值,所以与模板题不同,我们需要显式地存储 \(endpos\)
怎么求每个节点 \(endpos\) 呢?由于 SAM 拥有 fail 树,求一个节点的 \(endpos\) 集合可以对它在 fail 树上的子树的 \(endpos\) 取并(包括可能存在的它自己特有的 \(endpos\),这个在插入字符时就能解决),而解决像这样的子树类问题可以使用线段树合并,所以我们使用线段树合并。
这里需要注意,由于我们需要在访问一个节点在 fail 树上的祖先后访问这个节点,所以在线段树合并的时候要新建节点存储合并后的值。

实现

于是可以按照如下方式实现这个程序:
首先对 \(s\) 建出 SAM,给每个节点开一棵权值线段树;
接着用线段树合并求出每个节点的 \(endpos\)
对每个询问,对 SAM 进行 DFS
当能匹配上且能转移的时候,转移;如果往这条边转移有解,把这条边的字符加进字符串最后面,返回有解;
否则,找到大于 \(t_i\) 的最小转移边,把这个边的字符加进字符串最后面,返回有解;
如果还是无解,那么返回无解。
注意这样求出的字符串是倒过来的,而且数据不保证答案都是回文串,所以输出答案的时候需要反过来。

代码

#include <iostream>
#define N 200005
int n,q,li[N],buc[N];
std::string s,ans;
struct query {int l,r;std::string t;} a[N];
struct sgt
{
int d[N<<5],ls[N<<5],rs[N<<5],idx;
#define mid (lb+rb>>1)
void modify(int &x,int t,int k,int lb,int rb)
{
if(!x) x=++idx;
d[x]+=k;
if(lb==rb) return;
if(t<=mid) modify(ls[x],t,k,lb,mid);
else modify(rs[x],t,k,mid+1,rb);
}
int query(int x,int l,int r,int lb,int rb)
{
if(!x) return 0;
if(l<=lb&&rb<=r) return d[x];
int ret=0;
if(l<=mid) ret+=query(ls[x],l,r,lb,mid);
if(r>mid) ret+=query(rs[x],l,r,mid+1,rb);
return ret;
}
int merge(int x,int y,int lb,int rb)
{
if((!x)||(!y)) return x|y;
int nx=++idx;
d[nx]=d[x]+d[y];
if(lb<rb) ls[nx]=merge(ls[x],ls[y],lb,mid),rs[nx]=merge(rs[x],rs[y],mid+1,rb);
return nx;
}
#undef mid
} tre;
struct SAM
{
int tr[N<<1][26],fail[N<<1],len[N<<1],rt[N<<1],idx,last;
bool vis[N<<1];
void insert(char c,int pos)
{
c-='a';
int cur=++idx,p=last;
len[cur]=len[p]+1;
tre.modify(rt[cur],pos,1,1,n);
while(p&&!tr[p][c]) tr[p][c]=cur,p=fail[p];
if(!p) fail[cur]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fail[cur]=q;
else
{
int cq=++idx;
len[cq]=len[p]+1;
for(int i=0;i<26;i++) tr[cq][i]=tr[q][i];
fail[cq]=fail[q],fail[q]=fail[cur]=cq;
while(p&&tr[p][c]==q) tr[p][c]=cq,p=fail[p];
}
}
last=cur;
}
void build()
{
for(int i=1;i<=idx;i++) buc[len[i]]++;
for(int i=1;i<=n;i++) buc[i]+=buc[i-1];
for(int i=1;i<=idx;i++) li[buc[len[i]]--]=i;
for(int i=idx;i;i--) rt[fail[li[i]]]=tre.merge(rt[fail[li[i]]],rt[li[i]],1,n);
}
bool dfs(int k,int x,int id)
{
if(k<a[id].t.size())
{
int u=tr[x][a[id].t[k]-'a'];
if(u&&tre.query(rt[u],a[id].l+k,a[id].r,1,n))
if(dfs(k+1,u,id)) {ans.push_back(a[id].t[k]);return 1;}
}
int lim=0;
if(k<a[id].t.size()) lim=a[id].t[k]-'a'+1;
for(int i=lim;i<26;i++)
{
int v=tr[x][i];
if(v&&tre.query(rt[v],a[id].l+k,a[id].r,1,n))
{ans.push_back(i+'a');return 1;}
}
return 0;
}
} sam;
main()
{
sam.idx=sam.last=1;
std::ios::sync_with_stdio(0),std::cin.tie(0);
std::cin>>s>>q;
n=s.size();
for(int i=1;i<=n;i++) sam.insert(s[i-1],i);
sam.build();
for(int i=1;i<=q;i++)
{
std::cin>>a[i].l>>a[i].r>>a[i].t;
ans="";
if(!sam.dfs(0,1,i)) std::cout<<-1;
else for(int j=ans.size();j;j--) std::cout<<ans[j-1];
std::cout<<'\n';
}
}

最开始想的是离线处理,然后假了


\[\Huge End \]

posted @   整齐的艾萨克  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示