CodeForces - 1000F One Occurrence(主席树or莫队分块)

版权声明:本文为博主原创文章,未经博主允许不得转载。 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;
}

猜你喜欢

转载自blog.csdn.net/Cymbals/article/details/83794920