Codeforces 1401F Reverse and Swap

Description

给一个长度为 $2^n$ 的数组 $a$,现在需要处理 $q$ 个询问,每个询问是以下 4 种类型之一:

  • $Replace(x, k)$ 把 $a_x$ 修改为 $k$;
  • $Reverse(k)$ 对于每一个 $i(i\ge 1)$,把区间 $[(i-1)\cdot 2^k+1, i\cdot 2^k]$ 的元素翻转;
  • $Swap(k)$ 对于每一个 $i(i\ge 1)$,交换区间 $[(2i-2)\cdot2^k+1,(2i-1)\cdot2^k]$ 和 $[(2i-1)\cdot2^k+1,2i\cdot2^k]$ 的所有元素;
  • $Sum(l,r)$ 输出区间 $[l,r]$ 中所有元素的和。

$n \le 18$,$q \le 10^5$

Background

个人认为挺巧妙的一道题,结果在神仙眼里就是:

(语言经过删节)

Solution

操作写的不是人话,翻译一下四个操作:

1. $\operatorname{\large{R}\small{PLACE}}\normalsize{(x, k)}$,单点修改 $a_x \gets k$;
2. $\operatorname{\large{R}\small{EVERSE}}\normalsize{(k)}$,从左到右划分为若干个长度为 $2^k$ 的区间,将每个区间翻转;
3. $\operatorname{\large{S}\small{WAP}}\normalsize{(k)}$,从左到右划分为若干个长度为 $2^k$ 的区间,相邻两个区间交换位置;
4. $\operatorname{\large{S}\small{UM}}\normalsize{(l, r)}$,求 $\sum\limits_{i=l}^r a_i$。

我会做 1 和 4 !

考虑巧妙利用线段树,因为保证序列的长度为 $2^n$,所以,线段树的形状为:共 $n+1$ 层,令叶子结点为第 $0$ 层,根节点为第 $n$ 层,则第 $k$ 层的每个结点,维护的都是长度为 $2^k$ 的区间。

观察操作 2 和 3,我们发现,线段树第 $k$ 层的所有结点,恰好就是我们所想要修改的区间!

定义一个标记 $\text{rev}_{dep}$,如果为 $1$ 表示第 $dep$ 层的左右结点的左右儿子分别互换,为 $0$ 则不变。

先来看操作 3,如果我们将 $\text{rev}_{k+1}$ 改变,是不是,恰好相邻两个长度为 $2^k$ 的区间都互换了呢?

下图展示 $n = 3$,$\operatorname{\large{S}\small{WAP}}\normalsize{(1)}$:

再看操作 4,不难发现,它等价于改变所有的 $\text{rev}_0 \sim \text{rev}_k$。这很好理解,$\text{rev}_{k}$ 改变后,相当于是每个长度为 $2^k$ 的区间内部,前 $2^{k-1}$ 和后 $2^{k-1}$ 个调换了位置,但这两部分内部分别有序,所以要继续调换下去。

在加入了 $\text{rev}$ 数组后,线段树也不难实现,如果当前处在 $\text{rev}_{dep}=1$ 的一层,就把右儿子当作左儿子,左儿子当作右儿子好了。

时间复杂度 $\mathcal O(qn)$。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20, S = 1 << N;
int n, q;
bool rev[N];
struct segment_tree
{
	int o[S << 2];
	void build(int l, int r, int rt)
	{
		if(l == r) { scanf("%lld", &o[rt]); return; }
		int mid = l + r >> 1;
		build(l, mid, rt << 1); build(mid + 1, r, rt << 1 | 1);
		o[rt] = o[rt << 1] + o[rt << 1 | 1];
	}
	void modify(int l, int r, int rt, int dep, int p, int v)
	{
		if(l == r) { o[rt] = v; return; }
		int mid = l + r >> 1;
		if(p <= mid) modify(l, mid, rt << 1 | rev[dep], dep - 1, p, v);
		else modify(mid + 1, r, rt << 1 | rev[dep] ^ 1, dep - 1, p, v);
		o[rt] = o[rt << 1] + o[rt << 1 | 1];
	}
	int query(int l, int r, int rt, int dep, int ql, int qr)
	{
		if(ql <= l && r <= qr) return o[rt];
		int mid = l + r >> 1, res = 0;
		if(ql <= mid) res += query(l, mid, rt << 1 | rev[dep], dep - 1, ql, qr);
		if(qr > mid) res += query(mid + 1, r, rt << 1 | rev[dep] ^ 1, dep - 1, ql, qr);
		return res;
	}
} sgt;
#define Replace(x, k) sgt.modify(1, 1 << n, 1, n, x, k)
#define Sum(l, r) sgt.query(1, 1 << n, 1, n, l, r)
#define Swap(k) rev[k + 1] ^= 1
inline void Reverse(int k) { for(int i = 0; i <= k; i++) rev[i] ^= 1; }
signed main()
{
	scanf("%lld %lld", &n, &q);
	sgt.build(1, 1 << n, 1);
	while(q--)
	{
		int opt, x, y;
		scanf("%lld", &opt);
		switch(opt)
		{
			case 1:
				scanf("%lld %lld", &x, &y); Replace(x, y); break;
			case 2:
				scanf("%lld", &x); Reverse(x); break;
			case 3:
				scanf("%lld", &x); Swap(x); break;
			case 4:
				scanf("%lld %lld", &x, &y); printf("%lld\n", Sum(x, y)); break;
		}
	}
	return 0;
}
posted @ 2020-08-23 18:11  syksykCCC  阅读(501)  评论(1编辑  收藏  举报