【题解】简单题 By 5ab as a juruo

题目

题目来源:CCF CQOI2006;

评测地址:Luogu#5057

题目描述

有一个 (n) 个元素的数组,每个元素初始均为 (0)

(m) 条指令,要么让其中一段连续序列数字反转——(0)(1)(1)(0)(操作 1),要么询问某个元素的值(操作 2)。

例如当 n = 20 时,10 条指令如下:

辣鸡洛谷图床,又炸了

输入格式

第一行包含两个整数 (n)(m),表示数组的长度和指令的条数;

以下 (m) 行,每行的第一个数 (t) 表示操作的种类:

  • (t=1),则接下来有两个数 (L)(R),表示区间 ([L, R]) 的每个数均反转;
  • (t=2),则接下来只有一个数 (i),表示询问的下标。

输出格式

每个操作 2 输出一行(非 (0)(1)),表示每次操作 2 的回答。

数据规模及约定

评测时间限制 (1000 extrm{ms}),空间限制 (512 extrm{MiB})

  • 对于 (50\%) 的数据,(1le nle 10^3)(1le mle 10^4)
  • 对于 (100\%) 的数据,(1le nle 10^5)(1le mle 5 imes 10^5)

保证 (1le Lle Rle n)

分析

题意很清楚。将一个 (0/1) 数列区间求反,单点查询,是一道不折不扣的数据结构题。

看到区间修改,首先想到的就是线段树。但是 (mle 5 imes 10^5),线段树常数又大,很容易超时。所以,我们考虑树状数组。

注意到区间求反就是区间异或运算,所以可以用树状数组维护。再用区间查询差分来单点查询。实际测试 在不吸氧的情况下跑到将近 (100) ms。

Code

注意区分树状数组的单点/区间和实际操作的单点/区间,因为树状数组存储的实际上是差分操作后的原数列。

#include <cstdio>
#include <cctype>
using namespace std;

const int max_n = 100000;
bool tr[max_n+1] = {};
int n;

inline int lowbit(int x) { return x & -x; }

inline int read() // 10^6 的输入,加上快读比较保险
{
	int ch = getchar(), t = 1, n = 0;
	while (isspace(ch)) { ch = getchar(); }
	if (ch == '-') { t = -1, ch = getchar(); }
	while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
	return n * t;
}

void add(int p) // 树状数组单点修改
{
	while (p <= n)
	{
		tr[p] ^= true;
		p += lowbit(p);
	}
}

bool get(int p) // 树状数组的区间查询
{
	bool res = false;

	while (p > 0)
	{
		res ^= tr[p];
		p -= lowbit(p);
	}

	return res;
}

void modify(int l, int r) // 原数组的区间修改 <- 差分数组的单点修改
{
	add(l);
	
	if (r != n)
		add(r+1);
}

int main()
{
	int m, opt, ta, tb;

	n = read(), m = read(); // 输入
	for (int i = 0; i < m; i++)
	{
		opt = read(); // 编号

		if (opt == 1) // 修改操作
		{
			ta = read(), tb = read();
			modify(ta, tb);
		}
		else // 查询操作
		{
			ta = read();
			printf("%d
", get(ta)); // 原数组的单点查询 <- 差分数组的前缀和(误,应该是 xor)
		}
	}

	return 0;
}

后记

这道题比较卡常,如果线段树常数比较大的话可能就过不了。

做数据结构题时,最好用最适合的数据结构,而非功能最强大的。否则用暴力不香吗

原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-cqoi2006_lg5057.html