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})\)
考虑单次查询的过程,发现可拆分为下面几个部分:

  1. 整块内的逆序对数,已预处理得到,查询复杂度 \(O(\dfrac{n}{\sqrt{n}}) = O(\sqrt{n})\)
  2. 散块内的逆序对数,树状数组暴力即可,复杂度 \(O(\sqrt{n}\log \sqrt{n})\)
  3. 整块与整块间的逆序对。
  4. 散块与整块间的逆序对。

考虑如何得到上述 3,4 两项的贡献。

发现预处理整块之间的逆序对数时,需要遍历整个序列。
太浪费了!考虑顺便预处理第 3 项。
\(f_{l,r}\) 表示整块 \(l\sim r\) 的逆序对数。
对每个块都维护一个权值树状数组,在枚举到块 \(j\) 的某元素时,枚举之前块的树状数组。
设枚举到块 \(i\),查询比该元素大的数的个数,即为当前元素 与 块 \(i\) 的逆序对数,累加到 \(f_{j,i}\) 中。
这样做完后,\(f_{i,j}\) 存的只是块 \(i\) 和块 \(j\) 的逆序对数,DP 处理 \(f\),有转移:

\[f_{i,j} = f_{i,j} + f_{i,j-1} + \sum_{k=i+1}^{j}{f_{k,j}} \]

需要枚举每块的每一个数再枚举所有之前的块再在树状数组中查询。
总复杂度为 \(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. 整块内的逆序对数。
  2. 散块内的逆序对数。
  3. 整块与整块间的逆序对。
  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;
}
posted @ 2020-09-03 20:12  Luckyblock  阅读(152)  评论(2编辑  收藏  举报