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