笔记-一种将莫队强制在线的方法

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

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

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

考虑下莫队的原理:可以快速地由区间 \([l,r]\) 的答案转移到区间 \([l\pm 1,r\pm 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(n\sqrt n)\) 的曼哈顿路线行走,但若要强制在线,则只能按照题目询问的顺序走,运行时间完全由输入掌握。一种简单的想法是:构造多条路线

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

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

我目前使用的是将点放成一个矩形,设矩形大小为 \(S\times S\),则构造这些点的复杂度为 \(O(nS^2)\),而平面上被分为了 \(\frac nS\times \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,m\leq 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\n",las=base[bs[ix][iy]].work(l,r,k)); } return 0; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywspz.html