分块离线算法

CDQ分治

对应区间为$[l,r]$ ,中点为 $mid$

先递归左边计数

考虑左边对右边的贡献

递归右边计数

每次计算贡献

将$mid$左边染成白色

右边染成黑色

将这些二元组按第一关键字排序

排序后按顺序扫过去

用一些奇妙的数据结构维护,更新黑点的值

例1

题单Day3 T2

令$x_i=a_{1,i},y_i=a_{2,i},z_i=a_{3,i},w_i=a_{4,i}$

问题转化为求$f(i,j)=min(x_i+x_j,y_i+y_j,z_i+z_j,w_i+w_j)$

先钦定$x_i+x_j$最小

那么就有

$x_i+x_j<y_i+y_j$

$x_i+x_j<z_i+z_j$

$x_i+x_j<w_i+w_j$

$x_i-y_i<-(x_j-y_j)$

$x_i-z_i<-(x_j-z_j)$

$x_i-w_i<-(x_j-w_j)$

令$a_i=x_i-y_i,b_i=x_i-z_i,c_i=x_i-w_i$

那就是要使

$a_i<-a_j,b_i<-b_j,c_i<-c_j$

确定了一个$i$后,对答案的贡献就是

$t* x_i+\sum_{ok}x_j$

$t$为$ok$的个数

发现这玩意可以用cdq分治做

对于$x,y,z,w$分别跑一次

对于$max$把所有值取相反数,再跑4次cdq分治即可

整体二分

考虑一堆询问中,哪些答案会比mid小,放左边递归

反之放右边

当值域只剩一个数时,可以统计答案

细致讲解

大致代码格式

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

分块

例1

PPT p11

预处理$f(i,j)$表示第i块到第j块的区间的权值

$g(i,j)$ 表示第$i$块之前$j$出现的次数

对于每次询问

整块直接用

零散块算出每个数的答案,打擂台即可

例2

题单Day3T6

将操作序列分块(大小为T)

对于每个块处理

将操作序列按y从大到小排序

这样就可以只考虑当前连通性的问题(并查集)

但当前块可能有一些操作在询问之后

对于这些操作,直接暴力枚举

复杂度$O(T^2+mlogm+TlogT)=O(T^2+mlogm)$

当$T=\sqrt{mlogm}$ 复杂度最优$O(q\sqrt{mlogm})$

posted @ 2023-04-18 20:06  hubingshan  阅读(31)  评论(0)    收藏  举报  来源