• 分散层叠算法学习笔记


    这里不是洛谷题解,所以扯点题外话

    为何我会来学这个东西呢……因为有个大毒瘤 dq 要做分块,听说要用这个算法,我就找了论文,然后一起自闭……

    (这种理性愉悦的东西也不怎么实用吧

    勉强看完后,发现 command_block 在四月时就弄了个模板题……除了膜还能干啥呢(

    然而感觉肝疼,不是很能深入实现,所以现在先写这个半吊子博客弄点绪论。

    (还因为目前洛谷那里唯一的题解不是分散层叠


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

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

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


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

    • 给定 (k) 个有序序列,长度的和为 (n)(q) 次询问,每次给定数 (x),求出其在每个序列的后继。下文中认为,这个后继是非严格的。

    对于 (k=1) 是一个经典的二分算法,可以做到 (O(n)-O(log n))

    这个算法做 (k) 次即有 (O(n)-O(klogfrac{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+qlog 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
    ",x);
    		lstans=0;
    		int p=lower_bound(hhh+1,hhh+len[1]+1,x)-hhh;
    //		printf("first at %d
    ",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
    ",lstans);
    	}
    }
    
  • 相关阅读:
    导航守卫
    asnyc
    扩展运算符 ···
    模版字符串
    iterator迭代器
    箭头函数
    解构
    变量let const
    vueUI可视化
    python图像特征提取
  • 原文地址:https://www.cnblogs.com/1000Suns/p/14048210.html
Copyright © 2020-2023  润新知