题解 P6466 【分散层叠算法(Fractional Cascading)】

· · 题解

在我的博客看效果更佳:点这里

本文主要参考 IOI 2020 中国国家候选队论文《浅谈利用分散层叠算法对经典分块问题的优化》。

本文仅描述了该算法的基本思想及实现,更深入的理论及应用请在论文中查阅。(或者哪天我有闲心了来写

本文提供的代码并没有经过精细实现,同时依赖于模板题的一些特殊限制,所以不推荐作为模板使用,也不保证在一般情况下仍然有正确性。

分散层叠算法可以解决下述问题:

对于 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

比如,$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)$。 ```cpp #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); } } ```