這里不是洛谷題解,所以扯點題外話
為何我會來學這個東西呢……因為有個大毒瘤 dq 要做分塊,聽說要用這個算法,我就找了論文,然后一起自閉……
(這種理性愉悅的東西也不怎么實用吧
勉強看完后,發現 command_block 在四月時就弄了個模板題……除了膜還能干啥呢(
然而感覺肝疼,不是很能深入實現,所以現在先寫這個半吊子博客弄點緒論。
(還因為目前洛谷那里唯一的題解不是分散層疊
本文主要參考 IOI 2020 中國國家候選隊論文《淺談利用分散層疊算法對經典分塊問題的優化》。
本文僅描述了該算法的基本思想及實現,更深入的理論及應用請在論文中查閱。(或者哪天我有閑心了來寫
本文提供的代碼並沒有經過精細實現,同時依賴於模板題的一些特殊限制,所以不推薦作為模板使用,也不保證在一般情況下仍然有正確性。
分散層疊算法可以解決下述問題:
- 給定 \(k\) 個有序序列,長度的和為 \(n\)。\(q\) 次詢問,每次給定數 \(x\),求出其在每個序列的后繼。下文中認為,這個后繼是非嚴格的。
對於 \(k=1\) 是一個經典的二分算法,可以做到 \(O(n)-O(\log n)\)。
這個算法做 \(k\) 次即有 \(O(n)-O(k\log\frac{n}{k})\) 的復雜度。
另一種常見算法,是將這 \(k\) 個序列歸並成一個長為 \(n\) 的序列,同時對於每個元素記錄其后面第一個來自第 \(i\) 個序列的數是哪個。那么每次查詢都可以在大序列中二分,通過在大序列的后繼記錄的信息得到在每個序列的后繼。
這個算法的復雜度為 \(O(nk)-O(k+\log n)\),同時空間復雜度也是 \(O(nk)\)。
分散層疊算法可以 \(O(n)-O(k+\log n)\) 解決這個問題,且空間復雜度是 \(O(n)\)。
我們將一開始給出的第 \(i\) 個序列叫做 \(L_i\)。然后預處理另外 \(k\) 個有序序列 \(M_i\)。
\(M_k=L_k\),而對於 \(i<k\),\(M_i\) 由 \(L_i\) 和 \(M_{i+1}'\) 歸並得到。其中 \(M_{i+1}'\) 只包含了 \(M_{i+1}\) 下標為偶數的元素。
比如,\(L_2=[3,8,11,14,18],M_3=[9,12,13,15,17]\),那么 \(M_2=[3,8,11,12,14,15,18]\)。其中的 \(12\) 和 \(15\) 來自 \(M_3\)。
同時,對於 \(M_i\) 中的每一個元素,維護在 \(L_i\) 和 \(M_{i+1}\) 的后繼的下標(如果沒有,也記錄下來)。這個可以在歸並的時候維護。
預處理的時間復雜度,注意到 \(L_i\) 會在歸並 \(M_i\) 時貢獻 \(1\),歸並 \(M_{i-1}\) 時貢獻 \(\frac{1}{2}\),等等。所以每個序列的總貢獻系數不超過 \(2\),也即時空復雜度均為 \(O(n)\)。
對於一次詢問,先二分出 \(x\) 在 \(M_1\) 中的后繼,設為 \(y\)(如果沒有,還要特判)。
注意到 \(L_1\) 的元素在 \(M_1\) 中均有出現,所以 \(y\) 在 \(L_1\) 的后繼就是 \(x\) 在 \(L_1\) 的后繼。
我們也知道 \(y\) 在 \(M_2\) 的后繼。注意到 \(y\) 在 \(M_2\) 的后繼和 \(x\) 在 \(M_2\) 的后繼下標相差是 \(O(1)\) 的。
這是因為 \(x\) 在 \(M_2\) 的后繼,和這個后繼的后一個元素,恰好一個在 \(M_1\) 中出現,所以 \(y\) 肯定不會大於 \(x\) 在 \(M_2\) 的后繼的后一個,\(y\) 在 \(M_2\) 的后繼也不會。
所以如果我們知道 \(x\) 在 \(M_i\) 的后繼,就可以 \(O(1)\) 求出 \(x\) 在 \(M_{i+1}\) 和 \(L_i\) 的后繼。
此時我們就做到了 \(O(n)-O(k+\log n)\) 的復雜度。
下面給出洛谷 P6466 的代碼實現,時間復雜度為 \(O(nk+qk+q\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=22222,maxk=111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct node{
int v,nxt1,nxt2;
};
int n,k,q,d,lstans,a[maxk][maxn],tl,len[maxk],hhh[maxn];
node b[maxk][maxn],tmp[maxn];
void build(){
len[k]=n;
FOR(i,1,len[k]) b[k][i]=(node){a[k][i],i,0};
ROF(i,k-1,1){
tl=0;
FOR(j,1,len[i+1]) if(j%2==0) tmp[++tl]=b[i+1][j],tmp[tl].nxt2=j;
int cur=1,cur2=1;
FOR(j,1,n){
while(cur<=tl && tmp[cur].v<=a[i][j]){
tmp[cur].nxt1=j;
b[i][++len[i]]=tmp[cur];
cur++;
}
while(cur2<=len[i+1] && b[i+1][cur2].v<=a[i][j]) cur2++;
b[i][++len[i]]=(node){a[i][j],j,cur2};
}
while(cur<=tl){
tmp[cur].nxt1=n+1;
b[i][++len[i]]=tmp[cur];
cur++;
}
}
FOR(i,1,len[1]) hhh[i]=b[1][i].v;
/* FOR(i,1,k){
FOR(j,1,len[i]) printf("(%d,%d,%d) ",b[i][j].v,b[i][j].nxt1,b[i][j].nxt2);
puts("");
}*/
}
int main(){
n=read();k=read();q=read();d=read();
FOR(i,1,k) FOR(j,1,n) a[i][j]=read();
build();
FOR(_,1,q){
int x=read()^lstans;
// printf("work %d\n",x);
lstans=0;
int p=lower_bound(hhh+1,hhh+len[1]+1,x)-hhh;
// printf("first at %d\n",p);
FOR(i,1,k){
while(p<=len[i] && b[i][p].v<x) p++;
while(p>=2 && b[i][p-1].v>=x) p--;
if(p<=len[i]){
lstans^=a[i][b[i][p].nxt1];
p=b[i][p].nxt2;
}
else{
p=len[i+1]+1;
}
}
if(_%d==0) printf("%d\n",lstans);
}
}
