版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Cymbals/article/details/83794920
题目:http://codeforces.com/problemset/problem/1000/F
大意是给一个数列,q个询问,每次给一组(l,r),任意输出一个在这个区间内只出现过一次的数。
迟来的博客。
在桂林打自闭了,啥都不想写啥都不想干,到现在也是,还是把这篇博客补上,顺便回忆一下当时的思路。
首先把题目这个问题转化一下,思考如果一个数只在区间中出现过一次,那么它的前一个出现位置一定不在这个区间里,也就是小于l。如果出现了不止一次,肯定能找到一个位置,它的前一个出现位置在这个区间里。
因此可以对每一个位置,记录前一个出现位置,建主席树后就能两棵树相减来区间查询了。
但是要注意一点,当一个区间内有两个该数字出现时,我们想要查询到的是最后一个点,这时候就可以在插入后一个点的时候把前一个点设置为inf,防止查询到错误的点。
因为题目要求输出具体的数,所以主席树的节点要用pair存下前一个出现位置和自己。
(再换主席树板子我是狗,这个真好用)
ac代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int inf = 1e9;
int n, num[maxn], q;
struct Presitant_Tree {
int last[maxn], left[maxn];
int lc[maxn * 60], rc[maxn * 60], root[maxn];
int tot;
pii val[maxn * 60];
int newNode(pii p) {
val[tot] = p;
return tot++;
}
int newNode(int l, int r) {
lc[tot] = l;
rc[tot] = r;
val[tot] = min(val[l], val[r]);
return tot++;
}
int build(int l, int r) {
if(l == r) {
return newNode(make_pair(inf, 0));
}
int mid = (l + r) >> 1;
return newNode(build(l, mid), build(mid + 1, r));
}
int update(int i, int l, int r, int pos, int v) {
if(l == r) {
return newNode(make_pair(v, pos));
}
int mid = (l + r) >> 1;
if(pos <= mid) {
return newNode(update(lc[i], l, mid, pos, v), rc[i]);
}
return newNode(lc[i], update(rc[i], mid + 1, r, pos, v));
}
pii query(int i, int l, int r, int L, int R) {
if(l >= L && r <= R) {
return val[i];
}
pii ans = make_pair(inf, 0);
int mid = (l + r) >> 1;
if(L <= mid) {
ans = min(ans, query(lc[i], l, mid, L, R));
}
if(R > mid) {
ans = min(ans, query(rc[i], mid + 1, r, L, R));
}
return ans;
}
void init() {
tot = 1;
for(int i = 1; i <= n; i++) {
left[i] = last[num[i]];
last[num[i]] = i;
}
int cur = 0;
root[cur] = build(1, n);
for(int i = 1; i <= n; i++) {
cur = root[i - 1];
if(left[i]) {
cur = update(cur, 1, n, left[i], inf);
}
root[i] = update(cur, 1, n, i, left[i]);
}
}
void solve(int &l, int &r) {
pii ans = query(root[r], 1, n, l, r);
printf("%d\n", ans.first < l ? num[ans.second] : 0);
}
} pt;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
}
pt.init();
int l, r;
scanf("%d", &q);
while(q--) {
scanf("%d%d", &l, &r);
pt.solve(l, r);
}
return 0;
}
然后是莫队。
分块,跑莫队计数,然后按块暴力。
然而这样是过不了的。
题解提到了一个神奇的优化:
bool cmp(const Query &a, const Query &b) {
if(block[a.l] != block[b.l]) {
return a.l < b.l;
}
if(block[a.l] & 1) {
return a.r < b.r;
}
return a.r > b.r;
}
先按l排序,然后分奇偶,奇数按从小到大,偶数按从大到小。
简单来说,就是让r这个询问边界波浪式排序。
这样可以防止询问排序后出现巨大波动,维持莫队的复杂度。
ac代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int n, num[maxn], q, ans[maxn];
int block[maxn], bl[maxn], br[maxn];
int blocksize, blocknum;
void blocky() {
blocksize = sqrt(maxn);
for(int i = 1; i < maxn; i++) {
//判断是不是该分新的块了
block[i] = ((i - 1) / blocksize) + 1;
if(blocknum != block[i]) {
//上一块的右边界
br[blocknum] = i - 1;
blocknum = block[i];
//下一块的左边界
bl[blocknum] = i;
}
}
br[blocknum] = maxn - 1;
}
struct Query {
int l, r, id;
} qs[maxn];
bool cmp(const Query &a, const Query &b) {
if(block[a.l] != block[b.l]) {
return a.l < b.l;
}
if(block[a.l] & 1) {
return a.r < b.r;
}
return a.r > b.r;
}
int cnt[maxn], blockcnt[maxn], tot;
void remove(int x) {
--cnt[x];
if(cnt[x] == 1) {
++blockcnt[block[x]];
++tot;
} else if(cnt[x] == 0) {
--blockcnt[block[x]];
--tot;
}
}
void add(int x) {
++cnt[x];
if(cnt[x] == 1) {
++blockcnt[block[x]];
++tot;
} else if(cnt[x] == 2) {
--blockcnt[block[x]];
--tot;
}
}
int cal() {
if(!tot) {
return 0;
}
for(int i = 1; i <= blocknum; i++) {
if(!blockcnt[i]) {
continue;
}
for(int j = bl[i]; j <= br[i]; j++) {
if(cnt[j] == 1) {
return j;
}
}
}
return 0;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
}
blocky();
scanf("%d", &q);
for(int i = 1; i <= q; i++) {
scanf("%d%d", &qs[i].l, &qs[i].r);
qs[i].id = i;
}
sort(qs + 1, qs + q + 1, cmp);
tot = 0;
int l = 1, r = 1;
add(num[1]);
for(int i = 1; i <= q; i++) {
while(qs[i].l > l) {
remove(num[l++]);
}
while(qs[i].l < l) {
add(num[--l]);
}
while(qs[i].r > r) {
add(num[++r]);
}
while(qs[i].r < r) {
remove(num[r--]);
}
ans[qs[i].id] = cal();
}
for(int i = 1; i <= q; i++) {
printf("%d\n", ans[i]);
}
return 0;
}