bzoj3744. Gty的妹子序列
知识点: 分块
原题面:darkbzoj
题意简述
给定一长度为 \(n\) 的数列 \(a\),\(m\) 次查询操作。
每次查询给定参数 \(l,r\),求区间 \([l,r]\) 内的逆序对数。
强制在线。
\(1\le n,m\le 5\times 10^4\),\(1\le a_i\le 2^{31}-1\)。
分析题意
不强制在线是 CDQ 傻逼题。
强制在线,没有什么方便的数据结构维护,考虑分块。
这是一开始的想法:
按块大小为 \(\sqrt{n}\) 分块,树状数组暴力预处理每块的逆序对数,复杂度 \(O(\sqrt {n}\sqrt {n}\log \sqrt{n}) = O(n\log \sqrt{n})\)。
考虑单次查询的过程,发现可拆分为下面几个部分:
- 整块内的逆序对数,已预处理得到,查询复杂度 \(O(\dfrac{n}{\sqrt{n}}) = O(\sqrt{n})\)。
- 散块内的逆序对数,树状数组暴力即可,复杂度 \(O(\sqrt{n}\log \sqrt{n})\)。
- 整块与整块间的逆序对。
- 散块与整块间的逆序对。
考虑如何得到上述 3,4 两项的贡献。
发现预处理整块之间的逆序对数时,需要遍历整个序列。
太浪费了!考虑顺便预处理第 3 项。
设 \(f_{l,r}\) 表示整块 \(l\sim r\) 的逆序对数。
对每个块都维护一个权值树状数组,在枚举到块 \(j\) 的某元素时,枚举之前块的树状数组。
设枚举到块 \(i\),查询比该元素大的数的个数,即为当前元素 与 块 \(i\) 的逆序对数,累加到 \(f_{j,i}\) 中。
这样做完后,\(f_{i,j}\) 存的只是块 \(i\) 和块 \(j\) 的逆序对数,DP 处理 \(f\),有转移:
需要枚举每块的每一个数再枚举所有之前的块再在树状数组中查询。
总复杂度为 \(O(\sqrt{n}\sqrt{n}\sqrt{n}\log\sqrt{n}) = O(n\sqrt{n}\log\sqrt{n})\)。
是不是很扯淡
考虑第 4 项,由于散块较小,考虑直接枚举散块元素。
对于右侧散块,即查询整块中比它大的数的个数。
考虑主席树维护,左侧散块同理,查询比它小的数的个数即可。
复杂度 \(O(\sqrt{n}\log n)\)。
累计一下,总复杂度为 \(O(n\sqrt{n}\log\sqrt{n} +m\sqrt{n}\log n)\),\(n,m\) 同级,为 \(O(n\sqrt{n}\log n)\) 级别,瓶颈似乎在主席树上。
细节比较多,调调调过了几组手玩的数据,交上去 T 了。
常数过大被卡了。
发现预处理并不是复杂度瓶颈。
且得到的信息并不丰富,仅求出了连续的块的逆序对,dp 数组的大小仅为 \(O(\sqrt{n}\sqrt{n})=
O(n)\) 级别。
而预处理过程中枚举到了所有元素,且求出了它与之前所有块的逆序对数,却把信息压缩成这样,这显然不大合适。
考虑修改预处理对象,设 \(f_{l,r}\) 表示,从块 \(l\) 的开头,到 位置 \(r\) 的逆序对数。
预处理时枚举所有块,从它的开头,一直扫到整个数列的尾部,暴力用树状数组求逆序对。
复杂度 \(O(n\sqrt{n}\log n)\)。
再考虑单次查询时的 4 部分:
- 整块内的逆序对数。
- 散块内的逆序对数。
- 整块与整块间的逆序对。
- 散块与整块间的逆序对。
查询时,可直接得到第一个整块到查询区间右端点的逆序对数,第 1,3 项复杂度变为 \(O(1)\),且同时得到了右侧散块与它自身,与整块的逆序对数。
发现仅剩左侧散块与它自身,与整块的逆序对了。
考虑合并 2,4 两项,直接用主席树求。
枚举左侧散块中所有数,查询从 该数到右端点 内,比它大的数的个数。
复杂度 \(O(\sqrt{n}\log n)\)。
总复杂度不变,仍为 \(O(n\sqrt{n}\log n)\),但通过增大预处理的复杂度,减小了查询的巨大常数。
然后就可过了。
还有 \(O(n\sqrt n)\) 的预处理之王做法,通过归并来进行实现。
感觉比较神,之后再补。
爆零小技巧
树状数组必写懒惰删除。
块大小和块数量变量写反了= =
一下午,一个变量,好哥哥们我输了。
代码实现
\(O(n\sqrt n \log n)\)
//知识点:分块
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxm = 5e5;
const int kMaxn = 5e5 + 10;
const int kMaxSqrtn = 230 + 10;
//=============================================================
int n, m, block_size, block_num;
int root[kMaxn];
int maxa, map[kMaxn], a[kMaxn], data[kMaxn];
int bel[kMaxn], L[kMaxSqrtn], R[kMaxSqrtn], f[kMaxSqrtn][kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//ScientificConceptOfDevelopmentTree
namespace HjtTree{
#define ls (lson[now_])
#define rs (rson[now_])
#define mid (L_+R_>>1)
int node_num, lson[kMaxn << 4], rson[kMaxn << 4], size[kMaxn << 4];
void Insert(int &now_, int pre_, int L_, int R_, int val_) {
now_ = ++ node_num;
size[now_] = size[pre_] + 1;
ls = lson[pre_], rs = rson[pre_];
if (L_ == R_) return ;
if (val_ <= mid) Insert(ls, lson[pre_], L_, mid, val_);
else Insert(rs, rson[pre_], mid + 1, R_, val_);
}
int Query(int r_, int l_, int L_, int R_, int ql_, int qr_) {
if (ql_ > qr_) return 0;
if (ql_ <= L_ && R_ <= qr_) return size[r_] - size[l_];
int ret = 0;
if (ql_ <= mid) ret += Query(lson[r_], lson[l_], L_, mid, ql_, qr_);
if (qr_ > mid) ret += Query(rson[r_], rson[l_], mid + 1, R_, ql_, qr_);
return ret;
}
}
struct TreeArray {
#define lowbit(x) (x&-x)
int time, ti[kMaxm + 10], t[kMaxm + 10];
void Clear () {
time ++;
}
void Add(int pos_, int val_) {
for (; pos_ <= maxa; pos_ += lowbit(pos_)) {
if (ti[pos_] != time) {
t[pos_] = 0;
ti[pos_] = time;
}
t[pos_] += val_;
}
}
int Sum(int pos_) {
int ret = 0;
for (; pos_; pos_ -= lowbit(pos_)) {
if (ti[pos_] != time) {
t[pos_] = 0;
ti[pos_] = time;
}
ret += t[pos_];
}
return ret;
}
} tmp;
void Prepare() {
n = read();
for (int i = 1; i <= n; ++ i) {
a[i] = data[i] = read();
}
data[0] = - 0x3f3f3f3f;
std :: sort(data + 1, data + n + 1);
for (int i = 1; i <= n; ++ i) {
if (data[i] != data[i - 1]) maxa ++;
data[maxa] = data[i];
}
for (int i = 1; i <= n; ++ i) {
int ori = a[i];
a[i] = std :: lower_bound(data + 1, data + maxa + 1, ori) - data;
map[a[i]] = ori;
}
}
void PrepareBlock() {
int i = 1;
block_size = (int) sqrt(n);
block_num = n / block_size;
for (i = 1; i <= block_num; ++ i) {
L[i] = (i - 1) * block_size + 1;
R[i] = i * block_size;
}
if (R[block_num] < n) {
L[++ block_num] = R[block_num - 1] + 1;
R[block_num] = n;
}
for (int j = 1; j <= block_num; ++ j) {
for (int i = L[j]; i <= R[j]; ++ i) {
bel[i] = j;
}
}
for (int l = 1; l <= block_num; ++ l) {
tmp.Clear();
for (int r = L[l]; r <= n; ++ r) {
f[l][r] = f[l][r - 1] + tmp.Sum(maxa) - tmp.Sum(a[r]);
tmp.Add(a[r], 1);
}
}
}
//=============================================================
int main() {
Prepare();
PrepareBlock();
for (int i = 1; i <= n; ++ i) {
HjtTree :: Insert(root[i], root[i - 1], 1, maxa, a[i]);
}
m = read();
int lastans = 0;
while (m --) {
int l = read() ^ lastans, r = read() ^ lastans;
lastans = 0;
if (l > r) {
printf("0\n");
continue ;
}
if (bel[l] == bel[r]) {
tmp.Clear();
for (int i = l; i <= r; ++ i) {
lastans += tmp.Sum(maxa) - tmp.Sum(a[i]);
tmp.Add(a[i], 1);
}
printf("%d\n", lastans);
continue ;
}
lastans = f[bel[l] + 1][r];
for (int i = l; i <= R[bel[l]]; ++ i) {
lastans += HjtTree :: Query(root[r], root[i], 1, maxa, 1, a[i] - 1);
}
printf("%d\n", lastans);
}
return 0;
}
一开始的巨大常数做法
//知识点:分块
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxm = 5e4;
const int kMaxn = 5e4 + 10;
const int kMaxSqrtn = (int) 230 + 10;
//=============================================================
int n, m, block_size, block_num;
int root[kMaxn];
int maxa, map[kMaxn], a[kMaxn], data[kMaxn];
int bel[kMaxn], L[kMaxSqrtn], R[kMaxSqrtn], f[kMaxSqrtn][kMaxSqrtn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//ScientificConceptOfDevelopmentTree
namespace HjtTree{
#define ls (lson[now_])
#define rs (rson[now_])
#define mid (L_+R_>>1)
int node_num, lson[kMaxn << 5], rson[kMaxn << 5], size[kMaxn << 5];
void Insert(int &now_, int pre_, int L_, int R_, int val_) {
now_ = ++ node_num;
size[now_] = size[pre_] + 1;
ls = lson[pre_], rs = rson[pre_];
if (L_ == R_) return ;
if (val_ <= mid) Insert(ls, lson[pre_], L_, mid, val_);
else Insert(rs, rson[pre_], mid + 1, R_, val_);
}
int Query(int r_, int l_, int L_, int R_, int ql_, int qr_) {
if (ql_ <= L_ && R_ <= qr_) return size[r_] - size[l_];
int ret = 0;
if (ql_ <= mid) ret += Query(lson[r_], lson[l_], L_, mid, ql_, qr_);
if (qr_ > mid) ret += Query(rson[r_], rson[l_], mid + 1, R_, ql_, qr_);
return ret;
}
}
struct TreeArray {
#define lowbit(x) (x&-x)
int time, ti[kMaxm + 10], t[kMaxm + 10];
void Clear () {
time ++;
}
void Add(int pos_, int val_) {
for (; pos_ <= maxa; pos_ += lowbit(pos_)) {
if (ti[pos_] != time) {
t[pos_] = 0;
ti[pos_] = time;
}
t[pos_] += val_;
}
}
int Sum(int pos_) {
int ret = 0;
for (; pos_; pos_ -= lowbit(pos_)) {
if (ti[pos_] != time) {
t[pos_] = 0;
ti[pos_] = time;
}
ret += t[pos_];
}
return ret;
}
} t[kMaxSqrtn], tmp;
void Prepare() {
n = read();
for (int i = 1; i <= n; ++ i) {
a[i] = data[i] = read();
}
data[0] = - 0x3f3f3f3f;
std :: sort(data + 1, data + n + 1);
for (int i = 1; i <= n; ++ i) {
if (data[i] != data[i - 1]) maxa ++;
data[maxa] = data[i];
}
for (int i = 1; i <= n; ++ i) {
int ori = a[i];
a[i] = std :: lower_bound(data + 1, data + maxa + 1, ori) - data;
map[a[i]] = ori;
}
}
void PrepareBlock() {
int i = 1;
block_size = 100;//(int) sqrt(n);
block_num = n / block_size;
for (i = 1; i <= block_num; ++ i) {
L[i] = (i - 1) * block_size + 1;
R[i] = i * block_size;
}
if (R[block_num] < n) {
L[++ block_num] = R[block_num - 1] + 1;
R[block_num] = n;
}
for (int j = 1; j <= block_num; ++ j) {
for (int i = L[j]; i <= R[j]; ++ i) {
bel[i] = j;
for (int k = 1; k < j; ++ k) {
f[k][j] += t[k].Sum(maxa) - t[k].Sum(a[i]);
}
f[j][j] += t[j].Sum(maxa) - t[j].Sum(a[i]);
t[j].Add(a[i], 1);
}
}
for (int r = 1; r <= block_num; ++ r) {
for (int l = 1; l <= r; ++ l) {
f[l][r] += f[l][r - 1];
for (int k = l + 1; k <= r; ++ k) {
f[l][r] += f[k][r];
}
}
}
}
//=============================================================
int main() {
Prepare();
PrepareBlock();
for (int i = 1; i <= n; ++ i) {
HjtTree :: Insert(root[i], root[i - 1], 1, maxa, a[i]);
}
m = read();
int lastans = 0;
while (m --) {
int l = read() ^ lastans, r = read() ^ lastans;
lastans = 0;
if (l > r) {
printf("0\n");
continue ;
}
if (bel[l] == bel[r]) {
tmp.Clear();
for (int i = l; i <= r; ++ i) {
lastans += tmp.Sum(maxa) - tmp.Sum(a[i]);
tmp.Add(a[i], 1);
}
printf("%d\n", lastans);
continue ;
}
if (bel[l] + 1 <= bel[r] - 1) {
lastans = f[bel[l] + 1][bel[r] - 1];
for (int i = l; i <= R[bel[l]]; ++ i) {
lastans += HjtTree :: Query(root[R[bel[r] - 1]], root[L[bel[l] + 1] - 1], 1, maxa, 1, a[i]);
}
for (int i = L[bel[r]]; i <= r; ++ i) {
lastans += HjtTree :: Query(root[R[bel[r] - 1]], root[L[bel[l] + 1] - 1], 1, maxa, a[i] + 1, maxa);
}
}
tmp.Clear();
for (int i = l; i <= R[bel[l]]; ++ i) {
lastans += tmp.Sum(maxa) - tmp.Sum(a[i]);
tmp.Add(a[i], 1);
}
for (int i = L[bel[r]]; i <= r; ++ i) {
lastans += tmp.Sum(maxa) - tmp.Sum(a[i]);
}
tmp.Clear();
for (int i = L[bel[r]]; i <= r; ++ i) {
lastans += tmp.Sum(maxa) - tmp.Sum(a[i]);
tmp.Add(a[i], 1);
}
printf("%d\n", lastans);
}
return 0;
}