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; }