区间最长连续不重复子序列
题目链接
弱化版
给定一个长度为
要找到一对下标
我们希望区间长度
如果有多组解,就选
弱化版题解
这个简单版本有好几种可爱的解法呢!比如二分答案或者双指针,都能在
二分答案法:
我们先猜一个可能的区间长度
由于合法的长度是单调的(如果某个长度可行,那么更短的长度一定也可行),所以可以安心地二分答案。总时间复杂度是
双指针法:
维护两个指针
容易发现
小提示:如果用弱化版的
强化版
强化版在线做法(可以愉快地处理多次询问)
我们来定义两个辅助数组:
首先预处理一个
对于每个起点
因为当
要实现这个二分,我们需要能快速查询任意区间
这里有一个重要性质:
简单证明一下:假设存在
现在来看每次询问
- 起点
x 的“势力范围”超出了R :我们找到第一个满足L \le x \le R 且f_x > R 的位置x 。那么对于这个起点,它在询问区间内能贡献的长度就是R - x + 1 。由于f 单调,这个x 可以用二分快速找到。 - 起点落在
[L, x-1] 里:这些起点的f 值都不超过R ,所以它们能贡献的长度就是对应的g 值。我们只需要查询g 在区间[L, x-1] 上的最大值就好啦。
对于情况二,我们需要第二个数据结构(线段树或 ST 表)来维护
最终答案就是
这样我们就能高效地处理每一个询问了!
#include <bits/stdc++.h>
#define pb push_back
#define pii pair<int, int>
#define low_bit(x) ((x)&-(x))
using namespace std;
using ll = long long;
const int N = 2e5 + 5, M = 1e5 + 5;
int n, q, a[N];
int f[N], g[N], pre[N], d[M]; // d用于记录每个值最后出现的位置
int t1[N << 2], t2[N << 2]; // t1维护g,t2维护pre的最大值
void push_up(int num, int zt){
if(zt == 1) t1[num] = max(t1[num << 1], t1[num << 1 | 1]);
if(zt == 2) t2[num] = max(t2[num << 1], t2[num << 1 | 1]);
}
void build(int num, int l, int r, int zt){
if(l == r){
if(zt == 1) t1[num] = g[l];
if(zt == 2) t2[num] = pre[l];
return;
}
int mid = (l + r) >> 1;
build(num << 1, l, mid, zt);
build(num << 1 | 1, mid + 1, r, zt);
push_up(num, zt);
}
int qry(int num, int l, int r, int L, int R, int zt){
if(l > R || r < L) return 0;
if(L <= l && r <= R){
if(zt == 1) return t1[num];
return t2[num];
}
int mid = (l + r) >> 1;
return max(qry(num << 1, l, mid, L, R, zt),
qry(num << 1 | 1, mid + 1, r, L, R, zt));
}
// 检查区间 [l, x] 的 pre 最大值是否 < l
bool check(int l, int x){
return qry(1, 1, n, l, x, 2) < l;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
// 计算pre数组
for(int i = 1; i <= n; i++){
pre[i] = d[a[i]];
d[a[i]] = i;
}
// 建立维护pre最大值的线段树
build(1, 1, n, 2);
// 计算f和g数组
for(int i = n; i >= 1; i--){
int l = i + 1, r = n, ans = i;
while(l <= r){
int mid = (l + r) >> 1;
if(check(i, mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
f[i] = ans;
g[i] = ans - i + 1;
}
// 建立维护g最大值的线段树
build(1, 1, n, 1);
cin >> q;
while(q--){
int x, y; cin >> x >> y;
// 二分查找分界点p:第一个满足 f[p] > y 的位置
int l = x, r = y, p = y + 1;
while(l <= r){
int mid = (l + r) >> 1;
if(f[mid] > y) r = mid - 1, p = mid;
else l = mid + 1;
}
int ans = 0;
// 情况1:左端点为p,贡献为 y - p + 1
if(p != y + 1){
ans = y - p + 1;
if(p == x){ // 整个区间都属于情况1
cout << ans << '\n';
continue;
}
}
// 情况2:左端点在 [x, p-1] 中,贡献为g的最大值
cout << max(qry(1, 1, n, x, p - 1, 1), ans) << '\n';
}
return 0;
}