线段树 离线处理
问题描述:一个序列,q次询问。求区间[l, r]
中只出现一次的数(任意一个即可)。
思路:离线处理,用线段树。将询问按右端点进行排序,预处理pre数组。pre数组表示这个数上一次出现的下标。
如何用线段树进行操作?线段树可以用于单点修改,区间查询,只需要将这一题转换为此即可。区间查询查询一个pair的最小值pair<int,int>
,first
是上一次出现该数字的下标,second
是本次遍历的下标,这样对于每一次的查找来说,最小值的first
如果都大于或等于查询的ask.l
,就表示一定不存在解,就是0。否则,它下标对应得数字就是离ask.l
最近的只出现一次的数字。 如何进行单点修改呢。对于当前下标而言,对当前下标进行修改,将原来的PII值改为<上一次的下标位置,本次下标位置>
,同时也要对上一次的下标位置的点进行单点修改,修改值即为<INF, 随意吧(>
。
代码如下:
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <ctime>
#include <random>
#include <sstream>
#include <numeric>
#include <stdio.h>
#include <functional>
#include <bitset>
#include <algorithm>
using namespace std;
// #define Multiple_groups_of_examples
#define IOS std::cout.tie(0);std::cin.tie(0)->sync_with_stdio(false);
#define dbgnb(a) std::cout << #a << " = " << a << '\n';
#define dbgtt cout<<" !!!test!!! "<<endl;
#define rep(i,x,n) for(int i = x; i <= n; i++)
#define all(x) (x).begin(),(x).end()
#define pb push_back
#define vf first
#define vs second
typedef long long LL;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 2e6 + 21;
int w[N];
int a[N];
struct SegTree {
int l,r;
PII val; // vf 上一次出现的下标位置 vs 本次出现的下标位置
}tr[N << 2];
int ans[N], pre[N], color[N];
inline int ls(int u) {return u << 1; }
inline int rs(int u) {return u << 1 | 1; }
void pushup(int u) { // 找最小值
tr[u].val = min(tr[ls(u)].val, tr[rs(u)].val);
}
void build(int u, int l, int r) { // 建树
if(l == r) tr[u] = {l,r,{INF, INF}};
else {
tr[u] = {l,r,{INF,INF}};
int mid = l + r >> 1;
build(ls(u),l,mid); build(rs(u), mid + 1, r);
}
}
void modify(int u, int l, int r, PII reval) { // 将 [l,r] 区间进行修改,虽然就一个点 [l,l] [r,r] (
if(tr[u].l >= l && tr[u].r <= r) {
tr[u].val = reval;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(ls(u),l,r,reval);
if(r > mid) modify(rs(u), l, r,reval);
pushup(u);
}
PII query(int u, int l, int r) { // 查找最小值
if(tr[u].l >= l && tr[u].r <= r) {
return tr[u].val;
}
int mid = tr[u].l + tr[u].r >> 1;
PII tmp = {INF, INF};
if(l <= mid) tmp = query(ls(u),l,r);
if(r > mid) tmp = min(tmp, query(rs(u),l,r));
return tmp;
}
void inpfile();
void solve() {
int n; cin>>n;
// dbgtt
rep(i,1,n) {
cin>>a[i];
pre[i] = color[a[i]]; // 找上一次出现的下标
color[a[i]] = i; // 将该数字出现下标进行更新
}
build(1,1,n); // 建树
int q; cin>>q;
vector<array<int,3>> lit(q + 1); // l,r, i
rep(i,1,q) { // 离线处理, 需要知道答案下标
cin>>lit[i][0]>>lit[i][1];
lit[i][2] = i;
}
sort(lit.begin() + 1, lit.end(), [](array<int,3> pre, array<int,3> suf) {
return pre[1] < suf[1]; // 对 r 进行排序
});
for(int i = 1, j = 1; i <= q; ++i) {
while(j <= n && j <= lit[i][1]) {
modify(1,j,j,{pre[j], j}); // 对 [j,j] 区间进行修改
if(pre[j]) modify(1,pre[j],pre[j],{INF,pre[a[j]]}); // 如果该数字至少出现2次,将上一次的置INF
++j;
}
auto tmp = query(1,lit[i][0], lit[i][1]); // 得到第i次查询答案
if(tmp.vf < lit[i][0]) ans[ lit[i][2] ] = a[ tmp.vs]; // 如果上一次出现的下标位置小于查询l,则可以,反之不存在,为0
}
// 按查询顺序输出
rep(i,1,q) cout<<ans[i]<<'\n';
}
int main()
{
#ifdef Multiple_groups_of_examples
int T; cin>>T;
while(T--)
#endif
solve();
return 0;
}
void inpfile() {
#define mytest
#ifdef mytest
freopen("ANSWER.txt", "w",stdout);
#endif
}