分块离线算法
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})$

浙公网安备 33010602011771号