整体二分

因刚放假回来,zasdcn突然问我整体二分的精髓,然后发现自己跟没学过一样,啥都忘了,就特写此博,以助学习。

首先看板子(带修区间的第K小):

点击查看代码
struct Opt {
  int x, y, k, type, id;
  // 对于询问, type = 1, x, y 表示区间左右边界, k 表示询问第 k 小
  // 对于修改, type = 0, x 表示修改位置, y 表示修改后的值,
  // k 表示当前操作是插入(1)还是擦除(-1), 更新树状数组时使用.
  // id 记录每个操作原先的编号, 因二分过程中操作顺序会被打散
};

Opt q[N], q1[N], q2[N];
// q 为所有操作,
// 二分过程中, 分到左边的操作存到 q1 中, 分到右边的操作存到 q2 中.
int ans[N];
void add(int p, int x);
int query(int p);  // 树状数组函数, 含义见题3

void solve(int l, int r, int L, int R)
// 当前的值域范围为 [l,r], 处理的操作的区间为 [L,R]
{
  if (l > r || L > R) return;
  int cnt1 = 0, cnt2 = 0, m = (l + r) / 2;
  // cnt1, cnt2 分别为分到左边, 分到右边的操作数
  if (l == r) {
    for (int i = L; i <= R; i++)
      if (q[i].type == 1) ans[q[i].id] = l;
    return;
  }
  for (int i = L; i <= R; i++)
    if (q[i].type == 1) {  // 是询问: 进行分类
      int t = query(q[i].y) - query(q[i].x - 1);
      if (q[i].k <= t)
        q1[++cnt1] = q[i];
      else
        q[i].k -= t, q2[++cnt2] = q[i];
    } else
      // 是修改: 更新树状数组 & 分类
      if (q[i].y <= m)
        add(q[i].x, q[i].k), q1[++cnt1] = q[i];
      else
        q2[++cnt2] = q[i];
  for (int i = 1; i <= cnt1; i++)
    if (q1[i].type == 0) add(q1[i].x, -q1[i].k);  // 清空树状数组
  for (int i = 1; i <= cnt1; i++) q[L + i - 1] = q1[i];
  for (int i = 1; i <= cnt2; i++)
    q[L + cnt1 + i - 1] = q2[i];  // 将临时数组中的元素合并回原数组
  solve(l, m, L, L + cnt1 - 1), solve(m + 1, r, L + cnt1, R);
  return;
}

注:

l、r:答案值域区间,ql、qr:处理的询问(或修改)区间

我们是可以通过数据结构简单判断一下当前处理的操作中,哪些操作的val(询问是答案,修改就是修改的值了)是小于mid,还是大于。比如:

在第K小问题中:我们把修改操作中的小于等于mid的数统统看作1,加到相应的区间下标上。而询问操作则用前缀和判断区间\(<=\) mid的数是否是 \(<=\) k,若是,那把这个操作归到左区间 ( 划分到左右是根据答案值域走的,\(<=k\) 的那答案值域就在l到mid,所以要分到左边 )。然后好像就没了,整体二分的题好像就套个板子就行(其实是smtwy把我思路打断了,以后想起什么再写。)

posted @ 2022-08-08 18:33  5_Lei  阅读(80)  评论(6)    收藏  举报