NOIP复习题之基础线段树

nand

初始时你有一个空序列 \(a\) ,之后有 \(N\) 个操作。操作分为以下两种:

  • 1 x:在序列末尾插入一个元素 \(x\)\(x=0/1\)
  • 2 L R:定义 \(f(l,r)\) 表示 \(a_l \ \text{nand} \ a_{l+1} \dots \text{nand} \ a_r\)。查询 \(f(L,L)⊕f(L,L+1)⊕f(L,L+2)⊕\dots ⊕f(L,R)\)\(A \ \text{nand} \ B=!(A⊕B)\)

思路:

这道题可以通过找性质然后优化一下暴力可以过,但是这个性质真的是所有人都能想出来的吗?所以写线段树。

观察到一个很重要的条件是:值域仅为 \(0\)\(1\)。于是可以这么定义线段树:

\(sum_i\) 表示当前区间带入初值 \(i\) 进行计算 (将第一个数 nand 上 \(i\)) 后整个区间 nand 后的值。

\(psum_i\) 表示当前区间带入初值 \(i\) 进行计算 (将第一个数 nand 上 \(i\)) 后整个区间 \(f(L,L)⊕f(L,L+1)⊕f(L,L+2)⊕\dots ⊕f(L,R)\)$ 后的值。

那么转移有:

tree[p].sum[0] = tree[rs].sum[tree[ls].sum[0]];
tree[p].sum[1] = tree[rs].sum[tree[ls].sum[1]];
tree[p].psum[0] = (tree[ls].psum[0] ^ tree[rs].psum[tree[ls].sum[0]]);
tree[p].psum[1] = (tree[ls].psum[1] ^ tree[rs].psum[tree[ls].sum[1]]);

非常巧妙的一道题,这个思路值得学习。

Hotel

题目大致操作:寻找长度为 \(x\) 的连续段,区间赋值 \(0/1\)

思路:

线段树每个区间维护最长为 \(0\) 的连续段长度,这个维护显而易见。

寻找最靠左的长度为 \(x\) 的连续段时,分治考虑。如果左半区间存在长度大于等于 \(x\) 的连续段,递归左边。如果说左区间和右区间形成了一个长度大于等于 \(x\) 的连续段,那么可以直接计算出答案。(我们维护了每个区间的最靠左的连续段长度和最靠右的连续段长度)。右半区间同理。

int query(int p, int k) {
	if (tree[p].l == tree[p].r) return tree[p].l;
	push_down(p);
	if (tree[ls].maxn >= k) return query(ls, k);
	if (tree[ls].rsum + tree[rs].lsum >= k) {
		return tree[ls].r - tree[ls].rsum + 1;
	}
	if (tree[rs].maxn >= k) return query(rs, k);
} 

CF1045G AI robots

\(N\) 个机器人排成一行,第 \(i\) 个机器人的位置为 \(x_{i}\),视野为 \(r_{i}\),智商为 \(q_{i}\)。我们认为第 \(i\) 个 机器人可以看到的位置是 \([x_{i}-r_{i},x_{i}+r_{i}]\)
如果一对机器人相互可以看到,且它们的智商 \(q_{i}\) 的差距不大于\(K\),那么它们会开始聊天。
为了防止它们吵起来,请计算有多少对机器人可能会聊天。

思路:

虽然不是线段树的题,但是思路非常值得借鉴,对 cdq 理解更深一层。(用线段树做的话做法基于 \(k\) 的大小)

posted @ 2024-08-18 22:19  Otue  阅读(16)  评论(0)    收藏  举报