P2161 [SHOI2009] 会场预约

知识点:线段树

只会线段树,就只能用线段树来做,发现区间端点的范围不大,可以直接来维护,然后发现最重要的一个结论,那么就是我们对区间端点做统计的话,那么一定是成对出现的,因为题目的要求就是删掉和要加入线段有交集的线段,然后再加入线段,所以就是这些端点就是,第一个线段的左端点,第一个的右端点,第二个的左端点,第二个的右端点,以此类推,这样这个题就转化成了两个操作,一个区间修改为0,一个是单点增加,不是单点修改为1,因为我们要加入的区间可能左右端点是相同,然后我们分类讨论就行了,为的是寻找区间修改的左右端点位置,举例,如果要加入区间的两边的端点数区间和都是偶数,那么显然置零区间就是我们要加入的线段,如果两边都是奇数,怎么样,左边奇数右边偶数,左边偶数右边奇数,这样分类讨论一下这个题就行了,里面还用到线段树上二分,寻找第几个1在哪个位置,就算这个序列可能有值为2的点,那么也是可以这样子用的,看别人用的STL代码短时间快,以我的能力就只能先这样了

然后思考一下为什么这个题可以用树状数组做,单点增加不必说,求区间和也是,关键就是那个区间置零操作,很显然这个题我们输入的点的个数最多也就是n,那么删除的点最多也就这么多,如果删除的时候我们能够具体知道要删除的是拿个,单点操作一个一个删除显然是可以的,就是不知道如果把那些需要删除的点一个一个找出来,索性用线段树的区间修改一下推平,

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;

struct segt {
	int l, r, sum, tag;
} t[N * 4];

int m;

void pushdown(int p) {
	if (t[p].tag == -1) return;
	int p1 = p * 2, p2 = p * 2 + 1;
	t[p1].sum = t[p2].sum = t[p1].tag = t[p2].tag = 0;
	t[p].tag = -1;
}

void build(int p, int l, int r) {
	t[p].l = l; t[p].r = r; t[p].tag = -1;
	if (l == r) return;
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
}

void change(int p, int l, int r, int v) {
	if (l <= t[p].l && t[p].r <= r) {
		if (!v) t[p].sum = t[p].tag = 0;
		else t[p].sum++;
		return;
	}
	pushdown(p);
	int mid = (t[p].l + t[p].r) / 2;
	if (l <= mid) change(p * 2, l, r, v);
	if (r > mid) change(p * 2 + 1, l, r, v);
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}

inline int ask(int p, int l, int r) {
	if (l <= t[p].l && t[p].r <= r) return t[p].sum;
	pushdown(p);
	int sum = 0, mid = (t[p].l + t[p].r) / 2;
	if (l <= mid) sum += ask(p * 2, l, r);
	if (r > mid) sum += ask(p * 2 + 1, l, r);
	return sum;
}

inline int query(int p, int res) {
	if (t[p].l == t[p].r) return t[p].l;
	int p1 = p * 2;
	pushdown(p);
	if (res <= t[p1].sum) return query(p * 2, res);
	else return query(p * 2 + 1, res - t[p1].sum);
}

int main() {
	build(1, 0, 1e5 + 1);
	scanf("%d", &m);
	while (m--) {
		char op;
		int l, r;
		scanf("\n%c", &op);
		if (op == 'A') {
			scanf("%d%d", &l, &r);
			int tot = ask(1, 1, 1e5);
			int cnt = ask(1, l, r);
			int left = ask(1, 1, l - 1);
			int right = tot - cnt - left;
			int L, R;
			if (left % 2 == 0 && right % 2 == 0) {
				printf("%d\n", (tot - left - right) / 2);
				L = l; R = r;
			} else if (left % 2 && right % 2) {
				printf("%d\n", tot / 2 - left / 2 - right / 2);
				L = query(1, left);
				R = query(1, tot - right + 1);
			} else if (left % 2 == 0 && right % 2) {
				printf("%d\n", (cnt + 1) / 2);
				L = l;
				R = query(1, left + cnt + 1);
			} else {
				printf("%d\n", (cnt + 1) / 2);
				R = r;
				L = query(1, left);
			}
			change(1, L, R, 0);
			change(1, l, l, 1);
			change(1, r, r, 1);
		} else {
			printf("%d\n", ask(1, 1, 1e5) / 2);
		}
	}
	return 0;
}

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值