zoukankan      html  css  js  c++  java
  • 莫队算法入门

    Talk about 莫队

    莫队算法,是莫涛dalao发明的一个神奇的优化暴力算法,它使用看似很simple的指针移动操作以及分块的思想来将复杂度优化至(O(nsqrt n))

    莫队的基本思想也很简单:

    1. 离线操作,在后面会提到我们通过排序来降低复杂度
    2. 设之前我们以及求出了区间([l,r])的答案,那么我们考虑如何快速转移到([l+1,r],[l-1,r],[l,r-1],[l,r+1])
    3. 每一次利用之前的信息跳动指针即可得出答案

    不过如果是这样的话,只要出题人把数据造坑一点,让你(l,r)指针一直左右移动,就可以卡到(O(n^2))我还不如写暴力

    所以莫队的精髓来了,既然都是询问,那我们是否可以通过适当地改变询问的顺序来让(l,r)跳转的幅度更小一点。

    所有我们可以利用分块的思想来优化:对于两个询问,若在其(l)在同块,那么将其(r)作为排序关键字,若(l)不在同块,就将(l)作为关键字排序(这就是双关键字)

    这样就可以优化时间复杂度么,我们看一下严格的证明(摘自大米饼的博客):

    首先,枚举(m)个答案,就一个(m)了。设分块大小为(unit),元素(i)所属的快为(blk_i)

    分类讨论:

    (l)的移动:若下一个询问与当前询问的(l)所在的块不同,那么只需要经过最多(2cdot unit)步可以使得(l)成功到达目标.复杂度为:(O(mcdot unit))

    (r)的移动:(r)只有在(blk_l)相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳(n)次!),(blk_l)什么时候相同?在同一块里面(blk_i)相同。对于每一个块,排序执行了第二关键字: (r)。所以这里面的(r)是单调递增的,所以枚举完一个块,(r)最多移动n次。总共有(frac{n}{unit})个块:复杂度为:(O(frac{n^2}{unit}))

    总结:(O(ncdot unit+frac{n^2}{unit}))(n,m)同级,就统一使用(n)

    根据基本不等式得:当(unit=sqrt n)时,得到莫队算法的真正复杂度:(O(nsqrt n))

    然后除此以外莫队还有一个更加NB的常数优化,即在cmp时写成:

    return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));
    

    其实也很好理解吧,当左端点同块时判断一下当前快编号的奇偶性,尽量让右端点波动范围较小(类似波浪形)


    经典板子题——区间不同元素个数

    板子题参考:SPOJ D-query

    大致题意:给定一个数组,每次询问一个区间内有多少不同的元素

    很简单的莫队板子题,对于每一次加入新的数时都判断一下这个数之前是否出现过即可,删除时同理。

    别忘记离散化,CODE

    #include<cstdio>
    #include<cctype>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    const int N=30005,M=200005;
    struct data
    {
    	int l,r,ans,id;
    }q[M];
    int a[N],b[N],n,m,size,tot,blk[N],res,L,R,cnt[N];
    inline char tc(void)
    {
    	static char fl[100000],*A=fl,*B=fl;
    	return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
    }
    inline void read(int &x)
    {
    	x=0; char ch; while (!isdigit(ch=tc()));
    	while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    }
    inline void write(int x)
    {
    	if (x>9) write(x/10);
    	putchar(x%10+'0');
    }
    inline bool cmp1(data a,data b)
    {
    	return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));
    }
    inline bool cmp2(data a,data b)
    {
    	return a.id<b.id;
    }
    inline int find(int x)
    {
    	int l=1,r=tot,mid;
    	while (l<=r)
    	{
    		mid=l+r>>1; if (b[mid]==x) return mid;
    		if (b[mid]<x) l=mid+1; else r=mid-1;
    	}
    }
    inline void add(int col)
    {
    	if (++cnt[col]==1) ++res;
    }
    inline void del(int col)
    {
    	if (--cnt[col]==0) --res;
    }
    int main()
    {
    	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    	register int i; read(n); size=sqrt(n);
    	for (i=1;i<=n;++i) read(a[i]),b[i]=a[i],blk[i]=(i-1)/size+1;
    	sort(b+1,b+n+1); tot=unique(b+1,b+n+1)-b-1;
    	for (i=1;i<=n;++i) a[i]=find(a[i]);
    	for (read(m),i=1;i<=m;++i) read(q[i].l),read(q[i].r),q[i].id=i;
    	sort(q+1,q+m+1,cmp1); L=q[1].l; R=q[1].r;
    	for (i=L;i<=R;++i) add(a[i]); q[1].ans=res;
    	for (i=2;i<=m;++i)
    	{
    		while (L>q[i].l) add(a[--L]); while (L<q[i].l) del(a[L++]);
    		while (R<q[i].r) add(a[++R]); while (R>q[i].r) del(a[R--]);
    		q[i].ans=res;
    	}
    	for (sort(q+1,q+m+1,cmp2),i=1;i<=m;++i) write(q[i].ans),putchar('
    ');
    	return 0;
    }
    
  • 相关阅读:
    TypeError: expected string or bytes-like object
    Python之DataFrame更改列名及重排列顺序
    重启nginx 分类: ubuntu 测试 虚拟机 2014-12-12 11:50 126人阅读 评论(0) 收藏
    virtualbox下ubuntu调整分辨率的方法(给力!!!) 分类: ubuntu 虚拟机 2014-12-04 14:01 223人阅读 评论(0) 收藏
    安装Chrome driver/ IE driver 分类: python基础学习 2014-08-15 11:38 1328人阅读 评论(0) 收藏
    Python的静态方法和类成员方法 分类: python基础学习 2014-08-13 14:21 205人阅读 评论(0) 收藏
    linux 修改系统时间 分类: ubuntu 2014-07-28 12:04 209人阅读 评论(0) 收藏
    详解python linecache模块读取文件的方法 分类: python Module 2014-07-21 18:32 1057人阅读 评论(0) 收藏
    解决 WindowsError: [Error 87] 分类: 问题总结 2014-04-09 22:21 1266人阅读 评论(0) 收藏
    静态方法 分类: python基础学习 2014-04-05 19:34 228人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/cjjsb/p/9539388.html
Copyright © 2011-2022 走看看