「动态区间第k小值」P2617 Dynamic Rankings

知识点: 主席树,整体二分

原题面 Luogu

简述

给定一长度为 \(n\) 的数列 \(a\),给定 \(m\) 次操作。
操作有两种:

  1. 给定参数 \(x, y\),将 \(a_x\) 改为 \(y\)
  2. 给定参数 \(l,r,k\),查询数列 \(a\) 在闭区间 \([l,r]\) 内的第 \(k\) 小值。

\(1\le n,m\le 10^5\)\(|a_i|\le 10^9\)\(1\le l\le r\le n\)\(1\le k\le r-l+1\)
3S,512MB。

分析

前置知识:静态区间第 k 小值
先离散化,设数的最大值为 \(m\)

主席树

主席树的本质是 权值线段树的前缀和。
如果暴力修改,需要每次修改所有的位置,查询复杂度不变,修改复杂度为 \(O(n\log m)\)

如何平衡前缀和的修改操作?
考虑树状数组的思路,用二进制优化前缀和。

令第 \(k\) 棵权值线段树维护原来的 \([k-lowbit(k) + 1, k]\) 中的权值线段树。
修改,查询时都查询 \(\log n\) 个权值线段树,每次查询修改复杂度为 \(O(\log^2 m)\)
空间复杂度不变,仍为 \(O(n\log m)\)

整体二分

考虑静态问题时的整体二分,发现它天然支持修改操作。
考虑将一个修改操作拆成两份,分为减去原值 和 加上新值两部分。
之后按相同思路整体二分即可。

为什么拆修改是对的?感性理解一下。
整体二分实质上是对操作序列,按时间顺序进行二分。
能影响一个查询操作的仅有时间小于它的修改操作,保证了不会被之后的修改影响。

正常写法空间复杂度 \(O(n)\),时间复杂度 \(O(q\log^2 m)\)

代码

整体二分

//知识点:整体二分 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
const int kMaxn = 4e5 + 10; 
const int kInf = 1e9;
//=============================================================
struct Operation {
  bool type;
  int l, r, k, id;
  int pos, val, delta;
} q[kMaxn], lq[kMaxn], rq[kMaxn];
int n, m, maxa, ansnum, a[kMaxn], ans[kMaxn];
int data_num, data[kMaxn], map[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_;
}
namespace TreeArray {
  #define lowbit(x) (x&-x)
  const int kMaxm = 2e5;
  int time, ti[kMaxm + 10], t[kMaxm + 10];
  void Clear() {
    time ++;
  }
  void Add(int pos_, int val_) {
    for (; pos_ <= n; 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;
  }
}
void Prepare() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    q[i] = (Operation) {false, 0, 0, 0, 0, i, read(), 1};
    data[++ data_num] = a[i] = q[i].val; //a 赋初值 
  }
  for (int i = 1; i <= m; ++ i) {
    char opt[10]; scanf("%s", opt + 1);
    if (opt[1] == 'Q') {
      q[++ n] = (Operation) {true, read(), read(), read(), ++ ansnum, 0, 0, 0};
    } else {
      int pos = read(), val = read();
      q[++ n] = (Operation) {false, 0, 0, 0, 0, pos, a[pos], - 1};
      q[++ n] = (Operation) {false, 0, 0, 0, 0, pos, a[pos] = val, 1};
      data[++ data_num] = val;
    }
  }
  std :: sort(data + 1, data + data_num + 1);
  int lth = std :: unique(data + 1, data + data_num + 1) - data - 1;
  for(int i = 1; i <= n; i ++) {
    if (q[i].type) continue ;
    int ori = q[i].val;
    q[i].val = std :: lower_bound(data + 1, data + lth + 1, ori) - data;
    map[q[i].val] = ori;
    GetMax(maxa, q[i].val);
  }
}
#define mid ((l_+r_)>>1)
void Solve(int l_, int r_, int ql_, int qr_) {
  if (ql_ > qr_) return ;
  if (l_ == r_) {
    for (int i = ql_; i <= qr_; ++ i) {
      if (q[i].type) ans[q[i].id] = l_;
    }
    return ;
  }
  int pl = 0, pr = 0;
  for (int i = ql_; i <= qr_; ++ i) {
    if (! q[i].type) {
      if (q[i].val <= mid) {
        TreeArray :: Add(q[i].pos, q[i].delta);
        lq[++ pl] = q[i];
      } else {
        rq[++ pr] = q[i];
      }
    } else {
      int ret = TreeArray :: Sum(q[i].r) - TreeArray :: Sum(q[i].l - 1);
      if (q[i].k <= ret) {
        lq[++ pl] = q[i];
      } else {
        q[i].k -= ret;
        rq[++ pr] = q[i];
      }
    }
  }
  TreeArray :: Clear();
  for (int i = 1; i <= pl; ++ i) q[ql_ + i - 1] = lq[i];
  for (int i = 1; i <= pr; ++ i) q[ql_ + pl + i - 1] = rq[i];
  Solve(l_, mid, ql_, ql_ + pl - 1);
  Solve(mid + 1, r_, ql_ + pl, qr_);
}
//=============================================================
int main() {
  Prepare();
  Solve(1, maxa, 1, n);
  for (int i = 1; i <= ansnum; ++ i) {
    printf("%d\n", map[ans[i]]);
  }
  return 0;
}
/*
10 10 
54 33 94 24 90 56 40 71 63 28 
Q 7 9 2 
C 10 72 
C 10 81 
C 8 17 
C 9 39 
Q 3 7 3 
C 3 67 
C 3 21 
Q 1 8 2 
C 5 96 
*/ 
/*
63
56
21
*/ 

主席树

早期变量取名风格 + 写法。

//知识点:主席树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define lowbit(x) (x & -x)
#define ls (t[now].son[0])
#define rs (t[now].son[1])
#define mid (L + R >> 1)
#define ll long long
const int MARX = 1e6 + 10;

//=============================================================
struct TheScientificConceptOfDevelopmentTree {
  int val, son[2];
} t[MARX << 5];
struct Operation {
  bool type;
  int L, R, K;
  int Pos, Val;
} q[MARX];
int N, M, DataNum, Lth, Data[MARX], Num[MARX];
int NodeNum, Root[MARX];
int tmp[2][20], cnt[2];
//=============================================================
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 Modify(int &now, int L, int R, int Pos, int Val) {
  if (!now) now = ++NodeNum;
  t[now].val += Val;
  if (L == R) return;
  if (Pos <= mid)
    Modify(ls, L, mid, Pos, Val);
  else
    Modify(rs, mid + 1, R, Pos, Val);
}
void PrepareModify(int Pos, int Val) {
  int K = std ::lower_bound(Data + 1, Data + Lth + 1, Num[Pos]) - Data;
  for (int i = Pos; i <= N; i += lowbit(i)) Modify(Root[i], 1, Lth, K, Val);
}
int Query(int L, int R, int K) {
  if (L == R) return L;
  int size = 0;
  for (int i = 1; i <= cnt[1]; i++) size += t[t[tmp[1][i]].son[0]].val;
  for (int i = 1; i <= cnt[0]; i++) size -= t[t[tmp[0][i]].son[0]].val;

  if (K <= size) {
    for (int i = 1; i <= cnt[1]; i++) tmp[1][i] = t[tmp[1][i]].son[0];
    for (int i = 1; i <= cnt[0]; i++) tmp[0][i] = t[tmp[0][i]].son[0];
    return Query(L, mid, K);
  }
  for (int i = 1; i <= cnt[1]; i++) tmp[1][i] = t[tmp[1][i]].son[1];
  for (int i = 1; i <= cnt[0]; i++) tmp[0][i] = t[tmp[0][i]].son[1];
  return Query(mid + 1, R, K - size);
}
int PrepareQuery(int L, int R, int K) {
  memset(tmp, 0, sizeof(tmp));
  cnt[0] = cnt[1] = 0;
  for (int i = R; i; i -= lowbit(i)) tmp[1][++cnt[1]] = Root[i];
  for (int i = L - 1; i; i -= lowbit(i)) tmp[0][++cnt[0]] = Root[i];
  return Query(1, Lth, K);
}
//=============================================================
int main() {
  N = read(), M = read();
  for (int i = 1; i <= N; i++) Data[++DataNum] = Num[i] = read();
  for (int i = 1; i <= M; i++) {
    char Opt[5];
    scanf("%s", Opt);
    q[i].type = (Opt[0] == 'Q');
    if (q[i].type)
      q[i].L = read(), q[i].R = read(), q[i].K = read();
    else
      q[i].Pos = read(), Data[++DataNum] = q[i].Val = read();
  }
  std ::sort(Data + 1, Data + DataNum + 1);
  Lth = std ::unique(Data + 1, Data + DataNum + 1) - Data - 1;

  for (int i = 1; i <= N; i++) PrepareModify(i, 1);
  for (int i = 1; i <= M; i++)
    if (q[i].type)
      printf("%d\n", Data[PrepareQuery(q[i].L, q[i].R, q[i].K)]);
    else {
      PrepareModify(q[i].Pos, -1);
      Num[q[i].Pos] = q[i].Val;
      PrepareModify(q[i].Pos, 1);
    }
  return 0;
}
posted @ 2020-09-01 22:19  Luckyblock  阅读(296)  评论(2编辑  收藏  举报