当前位置: 首页 > news >正文

信息学奥赛一本通 1543:【例 3】与众不同

【题目链接】

ybt 1543:【例 3】与众不同

【题目考点】

1. RMQ问题

解决方法:

  • ST表
  • 线段树
2. 二分查找
3. 散列数组

【解题思路】

“完美序列”就是不重复元素构成的子段。以下将“完美序列”称为“不重复子段”
该题抽象后为:给定整数序列,每次查询一个区间中的最长不重复子段的长度。
设整个整数序列为a序列。
根据求最长上升子序列的经验,我们可以先求出以 a i a_i ai为结尾的最长不重复子段的长度,如果知道了子段的右端点 i i i与子段长度,子段的左端点(起始位置)自然也可以求出。
设数组 l e n len len l e n i len_i leni表示以 a i a_i ai为结尾的最长不重复子段的长度。
设数组 s t st st s t i st_i sti表示以 a i a_i ai为结尾的最长不重复子段的起始位置。
a i a_i ai为结尾的最长不重复子段一定不会包括a序列中 a i a_i ai前面的与 a i a_i ai相等的元素,需要能够取到每个元素前面与其相等的元素的下标。
设数组 l a s t last last l a s t i last_i lasti表示数值i上一次出现的下标。
注意:输入的数值范围为 ∣ a i ∣ ≤ 10 6 |a_i|\le 10^6 ai106,即 − 10 6 ≤ a i ≤ 10 6 -10^6\le a_i \le 10^6 106ai106,可能为负数。我们需要将输入的数值都增加 10 6 10^6 106,使 0 ≤ a i ≤ 2 ∗ 10 6 0\le a_i\le 2*10^6 0ai2106,这样数值就可以作为 l a s t last last数组的下标了。(或者也可以将 a a a序列离散化,或将last设为map)。
考虑求 s t i st_i sti的递推式:
首先,以 a i a_i ai为结尾的最长不重复子段的起始位置一定大于 l a s t a i last_{a_i} lastai,即大于等于 l a s t a i + 1 last_{a_i}+1 lastai+1
a i − 1 a_{i-1} ai1为结尾的最长不重复子段,肯定是由不重复元素构成的,其起始位置为 s t i − 1 st_{i-1} sti1。可以取其部分子段与 a i a_i ai构成以 a i a_i ai为结尾的最长不重复子段。

  • 如果 s t i − 1 ≥ l a s t a i + 1 st_{i-1}\ge last_{a_i}+1 sti1lastai+1,那么 s t i − 1 st_{i-1} sti1就是以 a i a_i ai为结尾的最长不重复子段的起始位置 s t i st_i sti。(因为第 s t i − 1 st_{i-1} sti1元素的前一个元素一定与以 a i − 1 a_{i-1} ai1为结尾的最长不重复子段中的某元素相同)。
  • 如果 s t i − 1 < l a s t a i + 1 st_{i-1}< last_{a_i}+1 sti1<lastai+1,那么 l a s t a i + 1 last_{a_i}+1 lastai+1就是以 a i a_i ai为结尾的最长不重复子段的起始位置 s t i st_i sti。(因为第 l a s t a i + 1 last_{a_i}+1 lastai+1元素的前一个元素 a l a s t a i a_{last_{a_i}} alastai a i a_i ai相同)。

因此有: s t i = max ⁡ { s t i − 1 , l a s t a i + 1 } st_i=\max\{st_{i-1}, last_{a_i}+1\} sti=max{sti1,lastai+1}
显然,st数组是升序的。
求出st数组后,已知最长不重复子段的右端点和左端点,自然可以求出子段长度,即len数组。 l e n i = i − s t i + 1 len_i=i-st_i+1 leni=isti+1

想要求区间 [ l , r ] [l, r] [l,r]中最长不重复子段的长度。
以区间 [ l , r ] [l, r] [l,r]中元素 a i a_i ai为结尾的最长不重复子段有两类:子段起始位置 s t i < l st_i<l sti<l,或子段起始位置 s t i ≥ l st_i\ge l stil
由于 s t st st是升序的,那么区间 [ l , r ] [l, r] [l,r]中一定存在一个位置 p p p,使得:

  • i ∈ [ l , p ] i\in[l, p] i[l,p]时, s t i < l st_i<l sti<l
  • i ∈ [ p + 1 , r ] i\in[p+1,r] i[p+1,r]时, s t i ≥ l st_i\ge l stil

在升序序列 s t st st中求满足 s t i < l st_i<l sti<l的最大的下标 p p p,可以通过二分查找完成。
找到 p p p后:

  • 对于 i ∈ [ l , p ] i\in[l, p] i[l,p],由于 s t i < l st_i<l sti<l,在 [ l , r ] [l, r] [l,r]内以 a i a_i ai为结尾的最长不重复子段的起始位置都是 l l l,自然子段 [ l , p ] [l, p] [l,p]的长度最长,长度为 p − l + 1 p-l+1 pl+1
  • 对于 i ∈ [ p + 1 , r ] i\in[p+1, r] i[p+1,r],由于 s t i ≥ l st_i\ge l stil,在 [ l , r ] [l, r] [l,r]内以 a i a_i ai为结尾的最长不重复子段的起始位置为 s t i st_i sti,长度为 l e n i len_i leni,其中最长的不重复子段的长度为 l e n len len序列区间 [ p + 1 , r ] [p+1, r] [p+1,r]的最大值。
    l e n len len序列的区间最值,可以对len序列建立ST表,每次区间查询的复杂度为 O ( 1 ) O(1) O(1)。或建立线段树,每次区间查询的复杂度为 O ( log ⁡ n ) O(\log n) O(logn),完成多次区间查询。
  • 比较 p − l + 1 p-l+1 pl+1 l e n len len序列区间 [ p + 1 , r ] [p+1,r] [p+1,r]中的最大值,求出二者的最大值,即为区间 [ l , r ] [l, r] [l,r]范围内最长不重复子段的长度。

注意:

  1. 当二分查找找到的 p = r p=r p=r时,区间 [ p + 1 , r ] [p+1,r] [p+1,r]是不存在的,查询 l e n len len序列区间 [ p + 1 , r ] [p+1,r] [p+1,r]中的最大值,应该让这一次查询返回0,这样可以不影响结果。
  2. 输入的查询区间 [ l , r ] [l, r] [l,r]的左右端点l与r都是从0开始的,本代码下标都是从1开始的,因此需要将l和r都增加1。

【题解代码】

解法1:ST表
#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define LN 25
int a[N], last[2000005], st[N], len[N], f[N][LN], lg[N];
void initST(int n)
{for(int i = 2; i <= n; ++i)lg[i] = lg[i/2]+1;//lg[i]:log_2(i)向下取整 for(int i = 1; i <= n; ++i)f[i][0] = len[i];for(int j = 1; j <= lg[n]; ++j)for(int i = 1; i+(1<<j)-1 <= n; ++i)f[i][j] = max(f[i][j-1], f[i+(1<<(j-1))][j-1]);
}
int query(int l, int r)
{if(l > r)//本题中当p为r时,传入p+1, r,会出现形参l>r的情况,该情况非法,返回长度0。 return 0;int k = lg[r-l+1];return max(f[l][k], f[r-(1<<k)+1][k]);
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);int n, m, l, r;cin >> n >> m;for(int i = 1; i <= n; ++i)//变为1~n {cin >> a[i];a[i] += 1000000;//输入的数值可能为负数,数值绝对值<=1e6,加上1e6后就是非负整数。 }for(int i = 1; i <= n; ++i){ st[i] = max(st[i-1], last[a[i]]+1);//st[i]:以a[i]为结尾的最长不重复子段的起始下标位置len[i] = i-st[i]+1;//len[i]:以a[i]为结尾的最长不重复子段的长度 last[a[i]] = i;//last[i]:数值i上一次出现的下标位置}initST(n);while(m--){cin >> l >> r;l++, r++;//变为从1开始int p = lower_bound(st+l, st+r+1, l)-st-1;cout << max(p-l+1, query(p+1, r)) << '\n';//query(p, r):使用ST表求len[p]~len[r]的最大值 }return 0;
}
解法2:线段树
#include<bits/stdc++.h>
using namespace std;
#define N 200005
struct Node
{int l, r, val;
} tree[4*N];
int a[N], st[N], len[N];
map<int, int> last;
void pushUp(int i)
{tree[i].val = max(tree[2*i].val, tree[2*i+1].val);
}
void build(int i, int l, int r)
{tree[i].l = l, tree[i].r = r;if(l == r){tree[i].val = len[l];return;}int mid = (l+r)/2;build(2*i, l, mid);build(2*i+1, mid+1, r);pushUp(i);
}
int query(int i, int l, int r)
{if(tree[i].r < l || tree[i].l > r)return 0;if(l <= tree[i].l && tree[i].r <= r)return tree[i].val;return max(query(2*i, l, r), query(2*i+1, l, r));
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);int n, m, l, r, p;cin >> n >> m;for(int i = 1; i <= n; ++i)//变为1~n cin >> a[i];for(int i = 1; i <= n; ++i){ st[i] = max(st[i-1], last[a[i]]+1);//st[i]:以a[i]为结尾的最长不重复子段的起始下标位置len[i] = i-st[i]+1;//len[i]:以a[i]为结尾的最长不重复子段的长度 last[a[i]] = i;//last[i]:数值i上一次出现的下标位置}build(1, 1, n);while(m--){cin >> l >> r;l++, r++;//变为从1开始int tl = l, tr = r;//二分找满足st[i]<l的最大的i while(tl < tr){int mid = (tl+tr+1)/2;if(st[mid] < l)tl = mid;elsetr = mid-1;}p = tl;cout << max(p-l+1, query(1, p+1, r)) << '\n';//query(p, r):使用ST表求len[p]~len[r]的最大值 }return 0;
}

相关文章:

  • mapbox进阶,切片网格生成实现
  • 洛谷 数楼梯 高精度
  • LangChain--(2)
  • 【Python系列】Python 中 yield 关键字
  • SpringCloudAlibaba怎么学?
  • Angular入门的环境准备步骤工作
  • 智能客服系统开发方案:RAG+多智能体技术实现
  • 【插件推荐】WebRTC Protect — 防止 IP 泄漏
  • Vite 及生态环境:新时代的构建工具
  • oceanbase导出导入数据csv
  • 基于 STM32 七段数码管显示模块详解
  • 圆与 π | 从几何之美到数学与物理的奇妙之旅
  • 快速解决软件测试的逻辑方法运用
  • 238. 除自身以外数组的乘积
  • Android 实体键盘 设置默认布局
  • LX-YST-1
  • BeckHoff_FB --> F_SEQ_X3_TrigJob 函数
  • 快速掌握Django框架设计思想(图解版)
  • 用数学融智学人力资源模型的核心架构:建立可量化的理论框架
  • Snipaste:一款简单强大的跨平台截图工具
  • 东营招标建设信息网/搜索引擎优化seo多少钱
  • 深圳网站建设网站设计软文推广/新冠疫情最新数据
  • wordpress怎么换行/搜索优化是什么意思
  • 成都网站推广排名/图片外链在线生成
  • 东莞网站开发推荐/如何建立一个自己的网站?
  • 前端开发做网站吗/百度seo关键词优化软件