最近做題的時候想到了一種將莫隊強制在線的方法,就是下面這道題:
給定長為 \(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;
}