%%%莫队
优化暴力?反正挺好用的
一些线段树树状数组很难维护的东西可以用莫队解决
区间修改就麻烦了。。。当然你可以分块
#普通莫队:
询问如区间[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 }
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 }
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; }
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; }
莫队这里还有坑,以后再填