CF1037H Security

一、题目

点此看题

二、解法

既然是要求字典序严格大于模式串又要最小,答案肯定长这样:模式串的一段+比模式串大的一个字符。

我们把模式串放在原串的后缀自动机上匹配,要保证能够匹配并且匹配到的是在 [ l , r ] [l,r] [l,r]区间中的子串,所以我们需要维护出一个 e n d p o s endpos endpos集合,用于判断上面那个,可以用线段树合并的方法维护。如果一个点的 e n d p o s endpos endpos [ l − l e n + 1 , r ] [l-len+1,r] [llen+1,r]处有值,就可以判断它在 [ l , r ] [l,r] [l,r]中出现过了。然后我们枚举比模式串大的一个字符,用上面的方法判断合法性,存下答案即可。

时间复杂度 O ( 26 × n log ⁡ n ) O(26\times n\log n) O(26×nlogn),如果有一些细节没有懂,可以看我的代码。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,tot,f[M],cnt,last,len[M],fa[M],ch[M][26];
char s[M];
int Index,rt[M],ls[20*M],rs[20*M],nxt[M];
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V) , next(N) {}
} e[2*M];
void ins(int &x,int l,int r,int id)
{
    x=++Index;//我们不需要维护其他的东西,有编号就表示出现过
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(mid>=id) ins(ls[x],l,mid,id);
    else ins(rs[x],mid+1,r,id);
}
void add(int c)
{
    int p=last,np=last=++cnt;
    ins(rt[np],1,n,len[np]=len[p]+1);//初始插入
    for(; p && !ch[p][c]; p=fa[p]) ch[p][c]=np;
    if(!p) fa[np]=1;
    else
    {
        int q=ch[p][c];
        if(len[q]==len[p]+1) fa[np]=q;
        else
        {
            int nq=++cnt;
            fa[nq]=fa[q];
            memcpy(ch[nq],ch[q],sizeof ch[q]);
            len[nq]=len[p]+1;
            fa[q]=fa[np]=nq;
            for(; p && ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
        }
    }
}
int merge(int x,int y)
{
    if(!x || !y) return x|y;
    int p=++Index;//新建节点放合并结果
    ls[p]=merge(ls[x],ls[y]);
    rs[p]=merge(rs[x],rs[y]);
    return p;
}
int ask(int i,int l,int r,int L,int R)//查询
{
    if(!i || L>r || l>R || L>R) return 0;
    if(L<=l && r<=R) return 1;
    int mid=(l+r)>>1;
    return ask(ls[i],l,mid,L,R)|ask(rs[i],mid+1,r,L,R);
}
void dfs(int u)
{
    for(int i=f[u]; i; i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa[u]) continue;
        dfs(v);
        rt[u]=merge(rt[u],rt[v]);//线段树合并
    }
}
int main()
{
    cnt=last=1;
    scanf("%s",s),n=strlen(s);
    for(int i=0; i<n; i++) add(s[i]-'a');
    for(int i=2; i<=cnt; i++)
    {
        e[++tot]=edge(fa[i],f[i]),f[i]=tot;
        e[++tot]=edge(i,f[fa[i]]),f[fa[i]]=tot;
    }
    dfs(1);
    m=read();
    while(m--)
    {
        int l=read(),r=read(),i=1,p=1,len=0;
        scanf("%s",s+1),len=strlen(s+1);
        for(;; i++) //枚举答案的长度
        {
            nxt[i]=-1;//存答案的数组,表示长度为i的答案的末位
            for(int j=max(0,s[i]-'a'+1); j<26; j++) //尝试填入一个比模式串大的字符
                if(ch[p][j] && ask(rt[ch[p][j]],1,n,l+i-1,r))
                {
                    nxt[i]=j;
                    break;
                }
            if(i==len+1) break;
            p=ch[p][s[i]-'a'];
            if(!p || !ask(rt[p],1,n,l-i+1,r)) break;
        }
        while(i && nxt[i]==-1) i--;
        if(!i) puts("-1");
        else
        {
            for(int j=1; j<i; j++) printf("%c",s[j]);
            printf("%c\n",nxt[i]+'a');
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值