LOJ2870「USACO 2018 US Open Platinum」Train Tracking【分块】
这是一道交互题
给定正整数 \(n,k\),按顺序输入长为 \(n\) 的自然数序列 \(c_i\) 两次,要求在仅使用 \(5500\) 个 \(\text{int}\) 的数组 \(S\) 的情况下,按顺序输出所有长为 \(k\) 的区间的最小值。你需要实现以下函数:
- \(\texttt{void helpBessie(int x)}\):
- \(x\) 表示当前输入的数。
- 你不能定义任何非常量的全局或静态变量。
当然 OJ 上好像限制不了
你可以调用以下函数:
- \(\texttt{int get(int index)}\):获取 \(S[index]\) 的值。
- \(\texttt{void set(int index, int value)}\):令 \(S[index]:=value\)。
- \(\texttt{void shoutMinimum(int output)}\):表示输出 \(output\) 这个数。
- \(\texttt{int getTrainLength()}\):获取 \(n\) 的值。
- \(\texttt{int getWindowLength()}\):获取 \(k\) 的值。
- \(\texttt{int getCurrentCarIndex()}\):获取当前输入的数的下标。
- \(\texttt{int getCurrentPassIndex()}\):若当前是
初见则返回 \(0\),否则返回 \(1\)。
你至多能调用 \(\texttt{set,get}\) 共 \(2.5\cdot 10^7\) 次。\(k\le n\le 10^6,c_i\le 10^9,\text{ML}=9\text{MiB}\)。
这是经典的滑动窗口问题,\(O(n)\) 空间的单调队列做法大家已经熟知。
在读入两遍的情况下,有一个只需 \(3\sqrt n+O(1)\) 个 \(\texttt{int}\) 的分块做法。
设 \(f_i\) 表示 \(c_i,c_{i+1},\cdots,c_{i+k-1}\) 的最小值的下标,待定阈值 \(B\)。
初见的时候求出 \(f_0,f_B,\cdots,f_{B\lfloor (n-k)/B\rfloor}\),这个只用在做单调队列的时候改一改,若当前点不是 \(B\) 的倍数且不比队尾优就不管它,于是单调队列只用塞 \(\frac nB\) 个元素。
第二次的时候按块做,设当前在处理 \([iB,(i+1)B)\),则 \(<f_{iB}\) 的元素不用考虑,\([f_{iB},(i+1)B)\) 这些点塞进单调队列里,\([(i+1)B,(i+1)B+k)\) 这些点一旦被加进来就不会弹出,开一个变量记录这个区间内当前已加入点的最小值。
需要注意的是,如果做下一块的时候又要把 \(f_{(i+1)B}\) 之后的点塞进单调队列重做一遍,肯定是不行的。
但如果当前已经输入到了 \(f_{(i+1)B}\) 这个位置,\((i+1)B\) 之前的答案就已经确定了,把答案输出之后直接紧接着做下一块即可。
取 \(B=\sqrt n\),时间复杂度 \(O(n)\)。
#include"grader.h"
#define ans fr==re?nowv:min(nowv,get(fr))
const int B = 1000, INF = 0x7fffffff;
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
template<typename T>
T min(const T &a, const T &b){if(a < b) return a; return b;}
void helpBessie(int v){
int n = getTrainLength(), k = getWindowLength(), id = getCurrentCarIndex();
if(getCurrentPassIndex()){
int pr = get(2*B); if(id < pr) return;
int fr = get(3*B), re = get(3*B+1), nowb = get(3*B+2), nowp = get(3*B+3), nowv = get(3*B+4), L = id-k+1;
if(id == pr){fr = re = nowb = nowp = 0; nowv = INF;}
if(id < (nowb+1)*B){
while(fr < re && v <= get(re-1)) --re;
set(re, v); set(re+B, id); ++re;
} else chmin(nowv, v);
if(L == nowp){while(fr < re && get(fr+B) < L) ++fr; shoutMinimum(ans); ++nowp;}
while(nowb < (n-k)/B && id == get(2*B+nowb+1)){
while(nowp <= (nowb+1)*B){
while(fr < re && get(fr+B) < nowp) ++fr;
shoutMinimum(ans); ++nowp;
} ++nowb; fr = 0; re = 1; nowv = INF; set(0, v); set(B, id);
} set(3*B, fr); set(3*B+1, re); set(3*B+2, nowb); set(3*B+3, nowp); set(3*B+4, nowv);
} else {
int fr = get(3*B), re = get(3*B+1), L = id-k+1;
if(!(id % B) || v <= get(re-1)){
while(fr < re && v <= get(re-1)) --re;
set(re, v); set(re+B, id); ++re;
} if(L >= 0 && !(L % B)){
while(fr < re && get(fr+B) < L) ++fr;
set(L/B + B*2, get(fr+B));
} set(3*B, fr); set(3*B+1, re);
}
}