因刚放假回来,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把我思路打断了,以后想起什么再写。)