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 @ 2025-02-19 23:30  整齐的艾萨克  阅读(14)  评论(0)    收藏  举报