CF793F Julia the snail 分块
分块的做法
分块是 CF 的官方题解的思路。虽说是
当然,这篇题解可能和官方题解有不同,但是基本思路和复杂度是一样的,请放心食用。
题意
一个坐标轴上,有若干线段,可以从每条线段的左端走到线段右端,也可以在坐标轴上沿负方向移动。保证每条线段右端点互不相同。
每次询问给一个区间
坐标轴长度
分析
因为线段的右端点互不相同,所以可以用一个数组
暴力
询问
从
为什么这样维护 Available 序列是正确的?当
分块
对坐标分块,设块长
新建一个数组
这样每次查询时,可以
预处理
为了优化常数,我们
处理一个数组
求
最后,正序扫描一遍
对于
-
f_{i, j} = f_{k, j} (k \in (i, g_{i, j}]) 因为这里的
i 是递减枚举的,所以对于k_1 < k_2 ,只要能取到k_2 ,一定能取到k_1 ,所以当f_{k_1, j} > f_{k_2, j} 时,k_2 的存在不能使答案更优,弹出k_2 。这里可以用单调队列维护
k 。保证队列中k 和f_{k, j} 单调递减。每一次求f_{i, j} 时,从队尾往前遍历队列,只要k \leq g_{i, j} ,就弹出这个k ,因为i < k ,且这时一定有f_{i, j} \geq f_{k, j} 。重复上面的操作直到k > g_{i, j} 。如果最后一个弹出的
k 满足f_{k, j} > g_{i, j} ,则赋值f_{i, j} = f_{k, j} 。否则就是下面的情况。 -
f_{i, j} = g_{i, j} 如果弹出的
k 满足f_{k, j} \leq g_{i, j} ,说明从i 直接走一条线段到达的点更优。所以赋值f_{i, j} = g_{i, j} 。
最后把
对于每个
算法复杂度是
块长
为了方便计算,因为
代码
注意:省略了缺省源和快读 RD()。
unsigned A[100005], B[100005], m, n, q, Ans(0), From[100005], f[100005][400], g[100005][400], BlockLen, BlockNum, Block[100405], Ql, Qr, Stack[100005];
int main() {
n = RD(), m = RD();
for (register unsigned i(1); i <= m; ++i) { // 先读进来, 因为求块长需要用 q
A[i] = RD(), B[i] = RD();
}
q = RD();
BlockLen = (n / (sqrt(q) + 1)) + 1; // 理论最优块长
BlockNum = (n + BlockLen - 1) / BlockLen; // 块数
for (register unsigned i(1); i <= BlockNum; ++i) {
for (register unsigned j(1); j <= BlockLen; ++j) {
Block[(i - 1) * BlockLen + j] = i; // O(n) 预处理 Block 数组
}
}
for (register unsigned i(1); i <= m; ++i) {
g[A[i]][Block[B[i]]] = max(g[A[i]][Block[B[i]]], B[i]); // 更新 g[l][Block[r]]
From[B[i]] = A[i]; // 存储 From[r] = l
}
for (register unsigned i(1); i <= n; ++i) {
g[i][Block[i]] = max(i, g[i][Block[i]]);
for (register unsigned j(Block[i]); j <= BlockNum; ++j) {
g[i][j] = max(g[i][j], g[i][j - 1]); // 扫一遍 g 数组, 通过单调性处理 g
}
}
for (register unsigned j(1), Ri; j <= BlockNum; ++j) {
Ri = j * BlockLen;
Ri = (Ri > n) ? n : Ri; // 终点的最大值 (第 j 块的右界)
for (register unsigned i(Ri), Hd(0); i >= 1; --i) {
while(Hd && Stack[Hd] <= g[i][j]) --Hd; // 弹出队尾
if(Stack[Hd + 1] <= g[i][j]) { // 这个 i 弹出了至少一次队尾
f[i][j] = max(f[Stack[Hd + 1]][j], g[i][j]);
}
else { // 一次也没弹出
f[i][j] = g[i][j];
}
Stack[++Hd] = i;
}
}
for (register unsigned i(1), j; i <= q; ++i) { // 回答询问
Ql = RD(), Qr = RD();
if(Block[Ql] ^ Block[Qr]) { // 左右端点在不同块内
Ans = f[Ql][Block[Qr] - 1];
j = ((Block[Qr] - 1) * BlockLen) + 1;
} else { // 在同一块内
Ans = Ql;
j = Ans + 1;
}
while (j <= Qr) { // 暴力最后不足一块的部分
if(Ql <= From[j] && From[j] <= Ans) {
Ans = j;
}
++j;
}
printf("%u\n", Ans);
}
return Wild_Donkey;
}