U423621 [HDK - NRC] Sqen Paradox

[HDK - NRC] Sqen Paradox

题目描述

给定一个长度为 \(n\) 的数列 \(S\).

询问在给定区间 \([l,r]\) 内最长的没有重复元素的区间长度.

输入格式

第一行两个整数 \(n,m\).

第二行 \(n\) 个整数,描述数列 \(S\).

随后 \(m\) 行,每行一个询问.

输出格式

\(m\) 行,请你对每个询问操作输出一行答案.

样例 #1

样例输入 #1

5 3
1 2 3 3 4
1 3
2 4
1 5

样例输出 #1

3
2
3

提示

样例解释

  1. 询问 1 3 ,连续的最长区间为 \([1,3]\).

  2. 询问 2 4,区间为 \([2,3]\).

  3. 询问 1 5,区间为 \([1,3]\).

数据约定

输入数据满足 \(n,m\le 10^{5},S_{i}\le 10^{9}\).

解析一(唐氏 \(O(n^2)\))(非正解)

(以下做法来自 \(GGrun\)

题干很简单,乍一看线段树呀,分块呀等数据结构。

但其实这道题要简单的多。

既然 找"没有重复元素的"区间,那么我们可以维护一个类似前缀的东西。

也就是找出每个点能向左延长多少。

所以只需要找出最后一个出现重复的数据就好了。

如图,\(1...6\) 前面都没有和自己重复的,直到又碰到一个 \(4\)

\(7\) 前面虽然没有和自己重复的,但是前面有一对 \(4\) ,所以只能到 \(4\)

综上:记录一个 \(k\) ,表示 最大长度只能为 \(k+1...i\) ,和每一个数上一次出现的位置。

取最大值更新 \(k\) ,就好啦!

(狂 \(\mathbb{D}\) 水数据)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+9;
int n,m,s[N],tot;
int cnt[N],qian[N];
map<int,int> mp;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		if(mp.find(s[i])==mp.end()) mp[s[i]]=++tot,s[i]=tot;
		else s[i]=mp[s[i]];
	}
	int k=0;
	for(int i=1;i<=n;i++)
	{
		k=max(k,cnt[s[i]]); cnt[s[i]]=i; 
		qian[i]=k;
	}
	while(m--)
	{
		int ans=1;
		int x,y;
		scanf("%d%d",&x,&y);
		for(int i=x;i<=y;i++)
		{
			ans=max(ans,i-max(x-1,qian[i]));
		}
		printf("%d\n",ans);
	}
	return 0;
}

解析二(\(O(nlogn)\)

上述唐氏做法经过 \(O(n^2)\) 的查询后成功 \(\mathbb{T}\) 了。

预处理没问题,查询可以分为两部分,一部分从 \(l\) 往右找断点 \(mid\)

另一部分找到断点后取 \((mid,r]\) 为右端点向左最大长度。

在预处理时找到左端点后我们顺便记录当前的右端点,也就是向右移的最大长度。

左端点向右扩展简单,\((mid,r]\) 找到的最大长度一定不会碰到左端点(易证:否则就在前一段)。

因为我们从左端点已经找出了一个长度 \(mid-x\) ,如果想要一个更大的并且起点不在左端点,那么它的右端点一定在 \((mid,r]\)

最大值用st表维护,复杂度 \(O(nlogn)\) .

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+9;
int n,m,s[N],tot;
int cnt[N],st[N][40],R[N];
unordered_map<int,int> mp;
int que(int l,int r)
{
	if(l>r) return 0;//注意
	int k=log2(r-l+1);
	return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		if(mp.find(s[i])==mp.end()) mp[s[i]]=++tot;
		s[i]=mp[s[i]];
	}
	int l=0;
	for(int i=1;i<=n;i++)
	{
		l=max(l,cnt[s[i]]); cnt[s[i]]=i; 
		R[l]=max(R[l],i);//R记右端点 
		st[i][0]=i-l;//区间为(l,i]
	}
	for(int i=1;i<n;i++)
		if(!R[i])  R[i]=R[i-1];//把中间那些被跳过去的点也更新一下 
	for(int j=1;j<=30;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
	while(m--)
	{
		int x,y;
		scanf("%d%d",&x,&y); x--;//因为前面计的(l,r],所以这里最远到x-1 
		int mid=min(y,R[x]);
		printf("%d\n",max(mid-x,que(mid+1,y)));
	}
	return 0;
}


特别鸣谢:Ishar-zdlint_R 提供思路。

2024.5.11 HDK 加强数据。

2024.8.8 找到原题/相似题???数列找不同

posted @ 2024-05-03 21:21  ppllxx_9G  阅读(107)  评论(10编辑  收藏  举报