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

    %%%莫队

    优化暴力?反正挺好用的

    一些线段树树状数组很难维护的东西可以用莫队解决

    区间修改就麻烦了。。。当然你可以分块

    #普通莫队:

    询问如区间[l,r]中有多少不同的数,或出现次数最多的数出现的多少次,无修改

    莫队较为适用的就是已知当前区间的答案能快速推出[l+1,r],[l-1,r],[l,r-1],[l,r+1]

    将l和r类似指针一样在区间上扫,然后通过离线询问,给询问排序来降低指针移动次数,复杂度$O(nsqrt{n})$

    排序:

    bel[a.l]==bel[b.l]?a.r<b.r:bel[a.l]<bel[b.l];
    

     奇偶性排序,对于同一个块,右端点单调上升或下降,波浪式移动减少移动次数:

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

    如:小B的询问:

    小B有一个序列,包含N个1~K之间的整数。他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重复次数。小B请你帮助他回答询问。

    对于全部的数据,1<=N、M、K<=50000

    直接莫队即可,开桶记录每个数在当前区间的出现次数,移动时减去即可

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 #include<cmath>
     6 #define MAXN 50005
     7 #define ll long long
     8 using namespace std;
     9 ll n,m,k,blo_num,s[MAXN],block[MAXN],l=1,r=0,sum[MAXN],ans=0;
    10 struct node{
    11     ll l,r,id,num;
    12 }ask[MAXN];
    13 bool cmp(node a,node b){
    14     return block[a.l]==block[b.l]?a.r<b.r:a.l<b.l;
    15 }
    16 bool CMP(node a,node b){
    17     return a.id<b.id;
    18 }
    19 void add(ll i){
    20     ans-=(sum[s[i]]*sum[s[i]]);
    21     sum[s[i]]++;
    22     ans+=(sum[s[i]]*sum[s[i]]);
    23 }
    24 void del(ll i){
    25     ans-=(sum[s[i]]*sum[s[i]]);
    26     sum[s[i]]--;
    27     ans+=(sum[s[i]]*sum[s[i]]);
    28 }
    29 int main(){
    30     scanf("%lld%lld%lld",&n,&m,&k);
    31     blo_num=(ll)pow(n,2.0/3.0);
    32     for(ll i=1;i<=n;i++){
    33         scanf("%lld",&s[i]);
    34         block[i]=i/blo_num;
    35     }
    36     for(ll i=1;i<=m;i++){
    37         scanf("%lld%lld",&ask[i].l,&ask[i].r);
    38         ask[i].id=i;
    39     }
    40     sort(ask+1,ask+m+1,cmp);
    41     for(ll i=1;i<=m;i++){
    42         while(l<ask[i].l) del(l++);
    43         while(l>ask[i].l) add(--l);
    44         while(r<ask[i].r) add(++r);
    45         while(r>ask[i].r) del(r--);
    46         ask[i].num=ans;
    47     }
    48     sort(ask+1,ask+m+1,CMP);
    49     for(ll i=1;i<=m;i++)
    50         printf("%lld
    ",ask[i].num);
    51     return 0;
    52 }
    View Code

    AHOI作业:https://www.cnblogs.com/Juve/p/11255827.html

    莫队套上了一个权值树状数组,其实还是一样的,用数据结构实现增点删点的作用

    NOIP模拟题:sum

    数学题也能用莫队做,推出式子发现符合莫队适用条件,直接上莫队

    https://www.cnblogs.com/Juve/p/11639891.html

    #二维莫队:csps模拟45蔬菜:https://www.cnblogs.com/Juve/protected/p/11576165.html

    数据水导致没有卡块长

    定义4个指针,其他的题解里都有

    #带修莫队:

    树套树当然可以解决,但是给莫队改造一下可以支持单点修改,

    另加一个时间戳t,和l,r指针作用差不多,记录每一个询问在那一个修改之后,然后判断当前t指针和询问的时间戳的关系,暴力修改

    比如数颜色:https://www.cnblogs.com/Juve/p/11379475.html

    #树上莫队:

    然额博主还没做过这样的题,先咕了

    #回滚莫队

    处理一些莫队不易维护的东西,比如区间每个数出现次数的最大值

    当区间移动时,加入一个数我们能够快速判断它是否比答案更优,但是当区间范围缩小时,我们不知道次大值在哪里

    这时用特殊的回滚莫队来实现

    只加不减的回滚莫队:当加点操作很好实现,但删点操作很难实现时(比如上面的例子)

    首先对原序列分块,按左端点块的升序和右端点的升序排序,保证左端点在同一个块内的询问右端点不降

    每到一个新的块,就把右端点置为这个块的最右端,左端点在右端点后面的一位(即指向一个空区间),然后对于每个块记录右端点移动时出现的最大值(可能的答案)sum

    对于左右端点在同一个块内的询问,暴力统计答案

    对于左端点在同一个块内的询问,起右端点递增,做添加操作(这一步很容易完成),同时更新sum

    移动左端点,做加点操作,记录临时变量tmp,初始时赋为当前sum,指针左移时更新tmp,但不更新sum,同时开一个栈记录在左端点在更新前的值

    左指针移动到指定位置后用tmp更新ans,然后回滚,左指针移回原位(r+1),并还原更新前的值(栈内元素)

    以同样的方式处理下一个块,复杂度依然是$O(nsqrt{n})$

    一般情况下块长选$frac{n}{sqrt{m}}$较优

    JOISC2014历史研究:给出n个数,求区间[l,r]中每个数×出现次数的最大值

    按以上方法即可

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #include<cmath>
     6 #define int long long
     7 using namespace std;
     8 const int MAXN=1e5+5;
     9 int n,q,a[MAXN],blo,bel[MAXN],tot,b[MAXN],num=0,c[MAXN];
    10 int l[MAXN],r[MAXN],L=1,R=0,las=0,cnt[MAXN],ans[MAXN],maxx=0,cntt[MAXN];
    11 struct node{
    12     int l,r,id;
    13     friend bool operator < (node x,node y){
    14         return bel[x.l]==bel[y.l]?x.r<y.r:x.l<y.l;
    15     }
    16 }ask[MAXN];
    17 signed main(){
    18     scanf("%lld%lld",&n,&q);
    19     blo=sqrt(n);
    20     for(int i=1;i<=n;++i){
    21         scanf("%lld",&a[i]);
    22         bel[i]=(i-1)/blo+1;
    23         b[i]=a[i];
    24     }
    25     sort(b+1,b+n+1);
    26     num=unique(b+1,b+n+1)-b-1;
    27     for(int i=1;i<=n;++i) c[i]=lower_bound(b+1,b+num+1,a[i])-b;
    28     tot=n/blo+(n%blo!=0);
    29     for(int i=1;i<=tot;++i){
    30         l[i]=(i-1)*blo+1;
    31         r[i]=i*blo;
    32     }
    33     r[tot]=n;
    34     for(int i=1;i<=q;++i){
    35         scanf("%lld%lld",&ask[i].l,&ask[i].r);
    36         ask[i].id=i;
    37     }
    38     sort(ask+1,ask+q+1);
    39     for(int i=1;i<=q;++i){
    40         if(bel[ask[i].l]==bel[ask[i].r]){
    41             for(int j=ask[i].l;j<=ask[i].r;++j) ++cntt[c[j]];
    42             for(int j=ask[i].l;j<=ask[i].r;++j)
    43                 ans[ask[i].id]=max(ans[ask[i].id],cntt[c[j]]*a[j]);
    44             for(int j=ask[i].l;j<=ask[i].r;++j) --cntt[c[j]];
    45             continue;
    46         }
    47         if(las!=bel[ask[i].l]){
    48             while(R>r[bel[ask[i].l]]) --cnt[c[R--]];
    49             while(L<r[bel[ask[i].l]]+1) --cnt[c[L++]];
    50             maxx=0,las=bel[ask[i].l];
    51         }
    52         while(R<ask[i].r){
    53             ++cnt[c[++R]];
    54             maxx=max(maxx,cnt[c[R]]*a[R]);
    55         }
    56         ans[ask[i].id]=maxx;
    57         int tmpl=L;
    58         while(tmpl>ask[i].l){
    59             ++cnt[c[--tmpl]];
    60             ans[ask[i].id]=max(ans[ask[i].id],cnt[c[tmpl]]*a[tmpl]);
    61         }
    62         while(tmpl<L) --cnt[c[tmpl++]];
    63     }
    64     for(int i=1;i<=q;++i){
    65         printf("%lld
    ",ans[i]);
    66     }
    67     return 0;
    68 }
    View Code

    mex:有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

    我们发现当撤销一个元素时,我们能判断当前元素是否为可行答案,但是加入就很麻烦

    这是一个只减不加的莫队,以左端点所在的块升序为第一关键字,以右端点降序序为第二关键字

    具体操作和只加不减的莫队差不多,只是我们让区间端点一直做撤销操作

    每到一个新块,就把左指针移动到块的最左端,右指针指向n,然后因为右指针单调不升,所以做删点操作

    代码留坑,因为这道题我不是用的回滚莫队

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define ll long long
    #define MAXN 800005
    using namespace std;
    ll n,m,a[MAXN],blo,block[MAXN],l=0,r=0,sum[MAXN],color[MAXN],res=0,ans[MAXN],b[MAXN];
    struct node{
        ll l,r,id;
    }ask[MAXN];
    bool cmp(node a,node b){
        return block[a.l]==block[b.l]?a.r<b.r:block[a.l]<block[b.l];
    }
    void add(ll x){
        if(x>=n) return ;
        if(color[x]==0) b[block[x]]++;
        color[x]++;
    }
    void del(ll x){
        if(x>=n) return ;
        color[x]--;
        if(color[x]==0) b[block[x]]--;
    }
    ll query(){
        for(ll i=1;i<=block[n];i++)
            if(b[i]!=blo){
                for(ll j=(i-1)*blo+1;j<=min(n,i*blo);j++)
                    if(!color[j]) return j;
            }
    }
    int main(){
        scanf("%lld%lld",&n,&m);
        blo=(ll)sqrt(n);
        for(ll i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            ++a[i];block[i]=(i-1)/blo+1;
        }
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&ask[i].l,&ask[i].r);
            ask[i].id=i;
        }
        sort(ask+1,ask+m+1,cmp);
        for(ll i=1;i<=m;i++){
            while(l<ask[i].l) del(a[l++]);
            while(l>ask[i].l) add(a[--l]);
            while(r<ask[i].r) add(a[++r]);
            while(r>ask[i].r) del(a[r--]);
            ans[ask[i].id]=query();
        }
        for(ll i=1;i<=m;i++) printf("%lld
    ",ans[i]-1);
        return 0;
    }
    View Code

    permu:

    莫队套线段树,复杂度$O(nsqrt{n}log_2n)$,代码中有明显卡常痕迹

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define MAXN 50005
    using namespace std;
    const int L=1<<20|1;
    char buffer[L],*S,*T;
    #define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++)
    inline int read(){
    	int x=0;char ch=getchar();
    	while(ch<'0'||ch>'9'){ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    	return x;
    }
    int n,m,a[MAXN],block[MAXN],blo,l=1,r=0,cnt[MAXN],ans[MAXN];
    struct node{
    	int l,r,id;
    	friend bool operator < (node a,node b){
    		return (block[a.l]^block[b.l])?block[a.l]<block[b.l]:((block[a.l]&1)?a.r<b.r:a.r>b.r);
    	}
    }ask[MAXN];
    struct Segtree{
    	Segtree *ls,*rs;
    	int le,ri,mi,l,r,sz;
    	Segtree(){}
    }*tr;
    void build(Segtree *&k,int l,int r){
    	k=new Segtree();
    	k->l=l,k->r=r;
    	if(l==r){
    		k->sz=1;
    		return ;
    	}
    	int mid=l+r>>1;
    	build(k->ls,l,mid);
    	build(k->rs,mid+1,r);
    	k->sz=k->ls->sz+k->rs->sz;
    }
    void update(Segtree *k){
    	k->le=k->ls->le,k->ri=k->rs->ri;
    	if(k->ls->le==k->ls->sz) k->le+=k->rs->le;
    	if(k->rs->ri==k->rs->sz) k->ri+=k->ls->ri;
    	k->mi=max(max(k->ls->mi,k->rs->mi),k->ls->ri+k->rs->le);
    }
    void change(Segtree *k,int opt,int val){
    	int l=k->l,r=k->r;
    	if(l==opt&&opt==r){
    		k->le=k->ri=k->mi=val;
    		return ;
    	}
    	int mid=(l+r)>>1;
    	if(opt<=mid) change(k->ls,opt,val);
    	if(opt>mid) change(k->rs,opt,val);
    	update(k);
    }
    void add(int x){
    	if(cnt[x]==0) change(tr,x,1);
    	cnt[x]++;
    }
    void del(int x){
    	cnt[x]--;
    	if(cnt[x]==0) change(tr,x,0);
    }
    int main(){
    	n=read(),m=read();
    	blo=sqrt(n);
    	for(int i=1;i<=n;i++){
    		a[i]=read();
    		block[i]=i/blo+1;
    	}
    	for(int i=1;i<=m;i++){
    		ask[i].l=read();
    		ask[i].r=read();
    		//scanf("%d%d",&ask[i].l,&ask[i].r);
    		ask[i].id=i;
    	}
    	build(tr,1,n);
    	sort(ask+1,ask+m+1);
    	for(int i=1;i<=m;i++){
    		while(l>ask[i].l) add(a[--l]);
    		while(l<ask[i].l) del(a[l++]);
    		while(r<ask[i].r) add(a[++r]);
    		while(r>ask[i].r) del(a[r--]);
    		ans[ask[i].id]=tr->mi;
    	}
    	for(int i=1;i<=m;i++)
    		printf("%d
    ",ans[i]);
    	return 0;
    }
    

    回滚莫队时间复杂度更优而且代码短

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define int long long
    using namespace std;
    const int MAXN=1e5+5;
    int n,m,blo,a[MAXN],bel[MAXN],r=0,gmx=0,lb[MAXN],rb[MAXN],top=0,ans[MAXN];
    struct node{
    	int l,r,id;
    	friend bool operator < (node p,node q){
    		return bel[p.l]==bel[q.l]?p.r<q.r:bel[p.l]<bel[q.l];
    	}
    }ask[MAXN];
    struct node1{
    	int opt,pos,val;
    }sta[MAXN<<6];
    signed main(){
    	scanf("%lld%lld",&n,&m);
    	blo=n/sqrt(m)+1;
    	for(int i=1;i<=n;++i){
    		scanf("%lld",&a[i]);
    		bel[i]=i/blo+1;
    	}
    	for(int i=1;i<=m;++i){
    		scanf("%lld%lld",&ask[i].l,&ask[i].r);
    		ask[i].id=i;
    	}
    	sort(ask+1,ask+m+1);
    	for(int i=1;i<=m;++i){
    		if(bel[ask[i].l]!=bel[ask[i-1].l]){
    			for(int j=1;j<=n;++j) lb[j]=rb[j]=0;
    			r=bel[ask[i].l]*blo;gmx=0;
    		}
    		while(r<ask[i].r){
    			r++;
                lb[a[r]]=lb[a[r]-1]+1;
                rb[a[r]]=rb[a[r]+1]+1;
                int tmp=lb[a[r]]+rb[a[r]]-1; 
                gmx=max(gmx,tmp);
                lb[a[r]+rb[a[r]]-1]=tmp;
                rb[a[r]-lb[a[r]]+1]=tmp;
    		}
    		int res=gmx;
    		top=0;
    		for(int l=ask[i].l;l<=min(bel[ask[i].l]*blo,ask[i].r);++l){
    			lb[a[l]]=lb[a[l]-1]+1,rb[a[l]]=rb[a[l]+1]+1;
    			int tmp=lb[a[l]]+rb[a[l]]-1;
    			res=max(res,tmp);
    			sta[++top]=(node1){1,a[l]+rb[a[l]]-1,lb[a[l]+rb[a[l]]-1]};
    			sta[++top]=(node1){2,a[l]-lb[a[l]]+1,rb[a[l]-lb[a[l]]+1]};
    			lb[a[l]+rb[a[l]]-1]=tmp;
    			rb[a[l]-lb[a[l]]+1]=tmp;
    		}
    		while(top){
    			if(sta[top].opt==1) lb[sta[top].pos]=sta[top].val;
    			else rb[sta[top].pos]=sta[top].val;
    			--top;
    		}
    		for(int l=ask[i].l;l<=min(bel[ask[i].l]*blo,ask[i].r);++l)
    			lb[a[l]]=rb[a[l]]=0;
    		ans[ask[i].id]=res;
    	}
    	for(int i=1;i<=m;++i) printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    莫队这里还有坑,以后再填

  • 相关阅读:
    HTTP深入浅出 http请求
    javascript 新兴的API
    javascript 高级技巧详解
    javascript AJAX与Comet详解
    php文件扩展名判断
    php创建新用户注册界面布局实例
    php使用递归创建多级目录
    php对文本文件进行分页功能简单实现
    php上传功能集后缀名判断和随机命名
    php判断数据库是否连接成功的测试例子
  • 原文地址:https://www.cnblogs.com/Juve/p/11664117.html
Copyright © 2011-2022 走看看