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);
	}
}
posted @ 2021-03-29 09:23  mizu164  阅读(122)  评论(0编辑  收藏  举报