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

    莫队算法

    普通莫队(二维)

    莫队算法要求询问能离线处理,并且对于两个相邻询问能够快速转移(通常是O(1)、O(log n))

    基础时间复杂度:L移动:(O(m*block)),R移动:(O(n^2/block))(block=n/sqrt(m))时整体复杂度最小,为(O(n*sqrt(m)))

    struct Q{
    	ll l,r,id;
    }query[maxn];
    bool cmp(Q a,Q b){
    	return (h[a.l]^h[b.l])?a.l<b.l:( (h[a.l]&1)?a.r<b.r:a.r>b.r);//奇偶性优化
    }
    int main(){
        ll block=n/sqrt(m);
        ll l=1,r=0;//闭区间
        res=0;
        for(ll i=1;i<=m;i++){
            ll &anss=ans[query[i].id];
            while(l<query[i].l)erase(a[l++]);
            while(l>query[i].l)insert(a[--l]);
            while(r>query[i].r)erase(a[r--]);
            while(r<query[i].r)insert(a[++r]);
            anss=res;
        }
    }
    

    带修莫队(三维)

    例如颜色计数问题,增加单点修改操作,此时需要再普通莫队的基础上再加上一维时间指针,称为“时间戳”,修改操作的编号就是时间轴,在输入查询操作时记录查询的时间点(上一次修改的编号),把每次修改操作记录在另外的数组中。

    排序时以L的分块为第一关键字,R的分块为第二关键字,time作为第三关键字

    struct Q{
    	int l,r,time,id;
    }q[maxn];
    bool cmp(Q a,Q b){
    	if(h[a.l]!=h[b.l]) return a.l<b.l;
    	if(h[a.r]!=h[b.r]) return a.r<b.r;
    	else return a.time<b.time;
    }
    bool cmp(Q a,Q b){
    	if(h[a.l]!=h[b.l]) return a.l<b.l;
    	if(h[a.r]!=h[b.r]) return a.r<b.r;
    	else return a.time<b.time;
    }
    int main(){
    	int l=1,r=0,now=0;
    	for(int i=1;i<=qnum;i++){
    		Q &qi=q[i];
    		while(l<qi.l) erase(l++);
    		while(l>qi.l) insert(--l);
    		while(r<qi.r) insert(++r);
    		while(r>qi.r) erase(r--);
    		while(now<qi.time) cht(++now,i);
    		while(now>qi.time) cht(now--,i);
    		ans[qi.id]=res;
    	}
    }
    

    基础时间复杂度:L和R同二维一样,time移动复杂度为(O(n^3/block)) ,整体复杂度为(O(m*block+n^2 /block+ n^3/block^2)) ,n,m量级相同时(block=n^{frac{2}{3}})时总复杂度最小,为(O(n^{frac{5}{3}}))

    例题

    数颜色

    带修莫队模板,同上

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int maxn=1e6+10;
    int a[maxn],h[maxn],block;
    struct Q{
    	int l,r,time,id;
    }q[maxn];
    struct C{
    	int p,v;
    }ch[maxn];
    bool cmp(Q a,Q b){
    	if(h[a.l]!=h[b.l]) return a.l<b.l;
    	if(h[a.r]!=h[b.r]) return a.r<b.r;
    	else return a.time<b.time;
    }
    int cnt[maxn];
    int res=0,ans[maxn];
    inline void insert(int i){
    	if(cnt[a[i]]++==0) res++;
    }
    inline void erase(int i){
    	if(--cnt[a[i]]==0) res--;
    }
    inline void cht(int now,int i){
    	if(q[i].l<=ch[now].p&&ch[now].p<=q[i].r){//修改的元素在询问区间内才会对答案造成影响 
    		if(--cnt[ a[ch[now].p] ]==0)res--;
    		if(cnt[ ch[now].v ]++==0) res++;
    	}
    	swap(ch[now].v,a[ch[now].p]);//下一次用到该修改时,相当于撤销修改,把颜色再改回去 
    }
    int main(){
    	int n,m;
    	cin>>n>>m;
    	block=pow(n,2/3);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=0;i<=n;i++) h[i]=i/block;
    	int time=0,qnum=0;
    	char c;
    	int x,y;
    	for(int i=1;i<=m;i++){
    		scanf(" %c%d%d",&c,&x,&y);
    		if(c=='Q'){
    			qnum++;
    			q[qnum].id=qnum;
    			q[qnum].time=time;
    			q[qnum].l=x;
    			q[qnum].r=y;
    		}
    		if(c=='R'){
    			ch[++time].p=x;
    			ch[time].v=y;
    		}
    	}
    	sort(q+1,q+1+qnum,cmp);
    	int l=1,r=0,now=0;
    	for(int i=1;i<=qnum;i++){
    		Q &qi=q[i];
    		while(l<qi.l) erase(l++);
    		while(l>qi.l) insert(--l);
    		while(r<qi.r) insert(++r);
    		while(r>qi.r) erase(r--);
    		while(now<qi.time) cht(++now,i);
    		while(now>qi.time) cht(now--,i);
    		ans[qi.id]=res;
    	}
    	for(int i=1;i<=qnum;i++) printf("%d
    ",ans[i]);
    }
    

    Chika and Friendly Pairs

    题意:给你一个数组,对于第i个数来说,如果存在一个位置j,使得j>i并且a[j]-k<=a[i]<=a[j]+k,那么这对数就称为好的,有q个询问,问你l到r区间有多少对好的数。

    离线询问,想到可以用莫队维护区间,新加入元素(或删除元素)x时要统计区间[x-k,x+k]内的元素个数,想到 可以利用树状数组存元素个数(cnt)(权值数组),区间和就是元素个数,数据<=1e9,因此需要离散化a[i],a[i]+k,a[i]-k,记离散化后对应的数组为p1,p2,p3,每次区间增加下标为i的元素时,用树状数组求(p3[i],p2[i])的元素和,同时update(p1[i],1)。删除元素同理,为了防止询问时统计到自身,需要先更新再询问。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int maxn=27005;
    const int maxv=maxn*3;
    typedef long long ll;
    ll res,ans[maxn];
    struct Q{
        ll l,r,id;
    }query[maxn];
    int h[maxn],a[maxn];
    int aa[maxn*3],T[maxn*3];
    int p1[maxn],p2[maxn],p3[maxn];
    bool cmp(Q a,Q b){
        return (h[a.l]^h[b.l])?a.l<b.l:( (h[a.l]&1)?a.r<b.r:a.r>b.r);
    }
    int lowbit(int i){
        return i &(-i);
    }
    void update(int i,int val){
        while(i<=maxv){
            T[i]+=val;
            i+=lowbit(i);
        }
    }
    int sum(int i){//求区间[1,i]内所有元素的和
        int res=0;
        while(i>0){
            res+=T[i];//从右往左累加求和
            i-=lowbit(i);
        }
        return res;
    }
    int _query(int l,int r){
        return sum(r)-sum(l-1);
    }
    inline void insert(int x){
        res+=_query(p3[x],p2[x]);
        update(p1[x],1);
    }
    inline void erase(int x){
        update(p1[x],-1);
        res-=_query(p3[x],p2[x]);
    }
    int main(){
        int n,m,k;
        cin>>n>>m>>k;
        int block=sqrt(n);
        for(int i=0;i<=n;i++) h[i]=i/block;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        int cnt=0;
        //离散化
        for(int i=1;i<=n;i++){
            aa[++cnt]=a[i];
            aa[++cnt]=a[i]+k;
            aa[++cnt]=a[i]-k;
        }
        sort(aa+1,aa+1+cnt);
        int size=unique(aa+1,aa+1+cnt)-(aa+1);
        for(int i=1;i<=n;i++){
            p1[i]=lower_bound(aa+1,aa+1+size,a[i])-aa;
            p2[i]=lower_bound(aa+1,aa+1+size,a[i]+k)-aa;
            p3[i]=lower_bound(aa+1,aa+1+size,a[i]-k)-aa;
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d",&query[i].l,&query[i].r);
            query[i].id=i;
        }
        sort(query+1,query+1+m,cmp);
        int l=1,r=0;
        for(int i=1;i<=m;i++){
            Q &q=query[i];
            while(l<q.l)erase(l++);
            while(l>q.l)insert(--l);
            while(r>q.r)erase(r--);
            while(r<q.r)insert(++r);
            ans[q.id]=res;
        }
        for(int i=1;i<=m;i++) printf("%d
    ",ans[i]);
    }
    
  • 相关阅读:
    .Net Micro Framework中文讨论组
    .Net Micro Framework 4.0正式开源
    php论坛学习的一个遍历的问题(学习) 简单
    Visual C++ 2008入门经典 第十章标准模板库(二) 简单
    PHP类型转换&&类型强制转换 简单
    Visual C++ 2008入门经典 第九章练习题 简单
    Visual C++ 2008入门经典 第十章标准模板库 简单
    PHP服务端推送技术Long Polling 简单
    Visual C++ 2008入门经典 第九章类的继承和虚函数(三) 简单
    正则表达式学习一 简单
  • 原文地址:https://www.cnblogs.com/ucprer/p/11351271.html
Copyright © 2011-2022 走看看