NOIP模拟赛 魔王降临(2) 洪流【区间最大字典序子串】

题目描述:

给定一个长度为N的字符串s和M个询问, 每次询问一个区间[l, r]的最大字典序子串。
输出共M行, 第i行输出一个整数, 表示你的答案子串的起始位置。
1 ≤ N,M ≤ 200000

题目分析:

首先,区间最大字典序子串一定是一段后缀。
我们把询问离线,按右端点从小到大排序,然后从左到右扫,扫到位置 p p p,就回答 r = p r=p r=p的询问。

对于一个固定的右端点,随着左端点的增加,答案子串的起始位置单调不降。

对于两个后缀 s [ l 1 , r ] s[l_1,r] s[l1,r] s [ l 2 , r ] s[l_2,r] s[l2,r],若 l 1 &lt; l 2 l_1&lt;l_2 l1<l2 s [ l 1 , r ] &lt; s [ l 2 , r ] s[l_1,r]&lt;s[l_2,r] s[l1,r]<s[l2,r],那么随着 r r r的增加,前者恒小于后者,且前者的区间包含后者的区间,所以不可能作为答案。
更一般的,对于两个后缀 s [ l 1 , r ] s[l_1,r] s[l1,r] s [ l 2 , r ] s[l_2,r] s[l2,r],若 s [ l 1 + L C P ( l 1 , l 2 ) ] &lt; s [ l 2 + L C P ( l 1 , l 2 ) ] s[l_1+LCP(l_1,l_2)]&lt;s[l_2+LCP(l_1,l_2)] s[l1+LCP(l1,l2)]<s[l2+LCP(l1,l2)],那么当 r &lt; l 2 + L C P ( l 1 , l 2 ) r&lt;l_2+LCP(l_1,l_2) r<l2+LCP(l1,l2)时,有 s [ l 1 , r ] &gt; s [ l 2 , r ] s[l_1,r]&gt;s[l_2,r] s[l1,r]>s[l2,r],当 r ≥ l 2 + L C P ( l 1 , l 2 ) r\ge l_2+LCP(l_1,l_2) rl2+LCP(l1,l2),有 s [ l 1 , r ] &lt; s [ l 2 , r ] s[l_1,r]&lt;s[l_2,r] s[l1,r]<s[l2,r]

所以我们可以在扫的时候用set维护可能作为答案的串的起始位置,询问[l,r],就在set里lower_bound(l)。
在这里插入图片描述
这道题充分利用了后缀比较的单调性。嗯,好题(膜高三学长出的膜你题QWQ。。虽然感觉像搬的一样好?)。

Code:

#include <bits/stdc++.h>
#define maxn 200005
using namespace std;
typedef unsigned long long ULL;
const ULL seed = 37;
ULL h[maxn],pw[maxn];
int n,m,ans[maxn];
set<int>suf;
vector<int>del[maxn],G[maxn],stk;
vector<pair<int,int> >q[maxn];
char s[maxn];
ULL Hash(int i,int j){return h[j]-h[i-1]*pw[j-i+1];}
int LCP(int i,int j){
	int l=0,r=min(n-i,n-j)+1,mid;
	while(l<r){
		mid=(l+r+1)>>1;
		if(Hash(i,i+mid-1)==Hash(j,j+mid-1)) l=mid;
		else r=mid-1;
	}
	return l;
}
void dfs(int u){
	if(suf.find(u)==suf.end()) return;
	suf.erase(u);
	for(int v: G[u]) dfs(v);
}
int main()
{
	scanf("%d%d%s",&n,&m,s+1);
	for(int i=1,l,r;i<=m;i++) scanf("%d%d",&l,&r),q[r].push_back(make_pair(l,i));
	pw[0]=1;
	for(int i=1;i<=n;i++) h[i]=h[i-1]*seed+s[i],pw[i]=pw[i-1]*seed;
	for(int i=1;i<=n;i++){
		while(!stk.empty()){
			int j=stk.back(),len=LCP(i,j);
			if(s[j+len]>s[i+len]) break;
			del[i+len].push_back(j);
			G[i].push_back(j);
			stk.pop_back();
		}
		stk.push_back(i),suf.insert(i);
		for(int j: del[i]) dfs(j);
		for(auto Q: q[i]) ans[Q.second]=*suf.lower_bound(Q.first); 
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值