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

6 篇文章 1 订阅
2 篇文章 0 订阅

题目: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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值