笔记-莫队的强制在线

最近做题的时候想到了一种将莫队强制在线的方法,就是下面这道题:

给定长为 (n) 的序列 (A) ,其中所有元素满足 (x in [1,n])(m) 次询问某个区间 ([l,r]) 内的第 (k) 小值,若某个数的出现次数大于 (w) ,则这个数在此询问中被视为 (n+1)(w) 是一给定常数,强制在线 (n,mleq 10^5)

正解分块即可,但凭着一颗爱捣腾的心,当然要用莫队来做,这种题若是离线用莫队解非常方便,但由于这题强制在线,不能直接上莫队

考虑下莫队的原理:可以快速地由区间 ([l,r]) 的答案转移到区间 ([lpm 1,rpm 1]) 的答案,复杂度分析大概是将一个对区间 ([l,r]) 的询问化为一个 ((l,r)) 的在二维平面上的点,由一个点 ((l_1,r_1))((l_2,r_2)) 的转移代价为 (|l_1-l_2|+|r_1-r_2|) 也即曼哈顿距离,整个算法相当于是一个点在平面上连出一条贯穿所有节点的链出来,复杂度即为这条链的总长(曼哈顿距离)。在对左端点分块、块内按右端点排序的情况下,设块大小为 (S),则复杂度为 (O(mS+frac {n^2}S)),在 (n,m) 同阶的情况下取 (S=sqrt n) 复杂度最优,为 (O((n+m)sqrt n))

但上面这种方法仅限于提前得知了所有的询问(即平面上点的位置),若是强制在线,这个做法可以被卡到 (O(nm)),于是就有了下面这个想法:在离线莫队的做法里,是让一个节点(初始状态)按照构造出的一条长度为 (O(nsqrt n)) 的曼哈顿路线行走,但若要强制在线,则只能按照题目询问的顺序走,运行时间完全由输入掌握。一种简单的想法是:构造多条路线

考虑在平面上保存多个节点,当前询问则由最近的节点进行移动求解,在保存足够多的点时,可以保证复杂度不受输入影响(相当于保存多个莫队进程)

则这样的复杂度为 “放置这些点的复杂度” + “离平面上最近的点曼哈顿距离的最大值”,复杂度完全依赖于放点的方案

我目前使用的是将点放成一个矩形,设矩形大小为 (S imes S),则构造这些点的复杂度为 (O(nS^2)),而平面上被分为了 (frac nS imes frac nS) 的块,则平面上离点最近距离的最大值约为 (frac n{2S}),则询问复杂度为 (O(frac n{2S}cdot m)),总复杂度 (O(nS^2+frac {nm}{2S})),若 (n,m) 同阶的情况下,取 (S=m^{frac 13}) 最优,时间与空间复杂度皆为 (O(m^{frac 23}n))

实际运行效率?在上面的这题中 ((n,mleq 10^5)),开 (mathrm {O2}) 优化,运行时间约为 (1.8s) (正解分块约为 (1.1s)),码量比正解小了不少

贴上代码:

#include <bits/stdc++.h>
using namespace std;

inline void read(int&x){
	char c11=getchar();x=0;while(!isdigit(c11))c11=getchar();
	while(isdigit(c11))x=x*10+c11-'0',c11=getchar();
}

const int N = 100103;
int a[N],id[N],L[N],R[N];
int bs[53][53],tot;

int n,Q,w;

struct Cap_Mo{
	int tng[N],cnt[331],bl,br;
	
	inline void add(int x){
		if(tng[x] == w) {cnt[id[x]] -= tng[x], ++tng[x]; return ;}
		++tng[x]; if(tng[x] <= w) ++cnt[id[x]];
	}
	
	inline void del(int x){
		if(tng[x] == w+1) {--tng[x], cnt[id[x]] += tng[x]; return ;}
		--tng[x]; if(tng[x] <= w) --cnt[id[x]];
	}
	
	void prework(int l,int r){
		bl = l, br = r;
		for(int i=l;i<=r;++i) add(a[i]);
	}
	
	int query(int k){
		int i,j;
		for(i=1;L[i];++i){
			if(k <= cnt[i]) break;
			k -= cnt[i];
		}
		for(j=L[i];j<=R[i];++j)
			if(tng[j] <= w){
				if(k <= tng[j]) return j;
				k -= tng[j];
			}
		return n+1;
	}
	
	int work(int ql,int qr,int k){
		int l = bl, r = br;
		while(ql < l) --l, add(a[l]);
		while(r < qr) ++r, add(a[r]);
		while(l < ql) del(a[l]), ++l;
		while(qr < r) del(a[r]), --r;
		
		int res = query(k);
		
		while(bl < l) --l, add(a[l]);
		while(r < br) ++r, add(a[r]);
		while(l < bl) del(a[l]), ++l;
		while(br < r) del(a[r]), --r;
		
		return res;
	}
}base[2213];

int main(){
	freopen("in","r",stdin);
	read(n), read(Q), read(w);
	for(int i=1;i<=n;++i) read(a[i]);
	
	int blk = sqrt(n*1.0), S = min(blk,47);
	for(int t=1,i=1;i<=n+1;i+=blk,++t){
		L[t] = i, R[t] = min(i+blk-1,n+1);
		for(int j=L[t];j<=R[t];++j) id[j] = t;
	}
	
	for(int i=1;i<=S;++i)
	for(int j=i;j<=S;++j)
		base[bs[i][j] = ++tot].prework(i*n/S,j*n/S);
	
	int Base_Blk = n / S;
	int l,r,k,las = 19260817;
	while(Q--){
		read(l), read(r), read(k);
		l ^= las, r ^= las, k ^= las;
		int ix = min(max(l/Base_Blk,1),S), iy = min(max(r/Base_Blk,1),S);
		int dl = abs(base[bs[ix][iy]].bl-l), dr = abs(base[bs[ix][iy]].br-r);
		if(ix != 1 and abs(base[bs[ix-1][iy]].bl-l) < dl) --ix;
		if(ix != iy and abs(base[bs[ix+1][iy]].bl-l) < dl) ++ix;
		if(iy != ix and abs(base[bs[ix][iy-1]].br-r) < dr) --iy;
		if(iy != n and abs(base[bs[ix][iy+1]].br-r) < dr) ++iy;
		printf("%d
",las=base[bs[ix][iy]].work(l,r,k));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/penth/p/10539133.html