莫队算法学习

%%%莫队

优化暴力?反正挺好用的

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

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

#普通莫队:

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

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

将l和r类似指针一样在区间上扫,然后通过离线询问,给询问排序来降低指针移动次数,复杂度$O(n\sqrt{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\n",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(n\sqrt{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\n",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\n",ans[i]-1);
    return 0;
}
View Code

permu:

莫队套线段树,复杂度$O(n\sqrt{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\n",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\n",ans[i]);
	return 0;
}

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

posted @ 2019-10-14 07:56  xukl21  阅读(199)  评论(1编辑  收藏  举报