莫队
莫队
https://www.cnblogs.com/WAMonster/p/10118934.html
summary
莫队算法是离线(需要预知所有询问)处理一类区间不修改查询类问题的算法。
如果你知道了[L,R]的答案。你可以在O(1)或O(logn)的时间得到[L,R-1]和 [L,R+1]和[L-1,R]和[L+1,R]的答案的话,就可以使莫队算法。
如果算完[L,R]后想知道[L’,R’]的答案, 代价为|L-L’|+|R-R’|。
如果把问[L,R]看平面上的点(L,R), 由1个点到另1个点的花费就是两点间的曼哈顿距离 |x1-x2|+|y1-y2|——欧几里得距离sqrt( (x1-x2)^2 + (y1-y2)^2 )
那么我们需要的计算量实际上是一棵曼哈顿距离意义下的最小生成树的树边长度和。
可以证明最劣复杂度是\(O(n∗\sqrt(n))\)
曼哈顿最小生成树
可证明有用的点是每45°里曼哈顿距离最近的点。(把坐标系分成“米”字)
边数m就是8n,生成最小生成树算法(mlogm)解决。
不过复杂度太高,我们一般用分块替代。
对序列分块。然后对于所有询问按L所在块的编号排序。如果一样再按R排。然后按排后的顺序计算。
复杂度分两情况考虑:
若Li 与Li+1 在同一块里; 若Li与Li+1不在同一块里。 设块大小为B复杂度为B*N+𝑁/B∗𝑁
特别的 转移复杂度为O(1)时,B取sqrt(n)时, 复杂度为O(𝑛sqrt(n))
https://www.cnblogs.com/DaD3zZ-Beyonder/p/5795636.html
https://www.cnblogs.com/beiyuoi/p/5795800.html
理解
初探https://www.luogu.com.cn/blog/codesonic/Mosalgorithm
进阶(有卡常技巧可看)https://www.cnblogs.com/WAMonster/p/10118934.html
一些裸题
小z的袜子
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
inline int read() {
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x;
}
const int N=50005;
int n,m,c[N];
int size,b[N];
int cnt[N];
LL fz[N],fm[N],ans;
LL C(int x) {return 1ll*x*(x-1)/2;}
struct query {
int l,r,id;
bool operator < (const query &x) const {
return ((l-1)/size+1)==((x.l-1)/size+1) ? r<x.r : ((l-1)/size+1)<((x.l-1)/size+1);
}
}q[N];//关键在于排序
inline void upd(bool add,int pos) {
int x=c[pos];
ans-=C(cnt[x]);
add?cnt[x]++:cnt[x]--;
ans+=C(cnt[x]);
}
LL gcd(LL a,LL b) {
return b?gcd(b,a%b):a;
}
int main() {
n=read();m=read();
size=sqrt(n);
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r,id=q[i].id;
while(r<nowr) upd(1,++r);//顺序不能变
while(l>nowl) upd(1,--l);
while(l<nowl) upd(0,l++);
while(r>nowr) upd(0,r--);
if(nowl==nowr) fz[id]=0,fm[id]=1;
else fz[id]=ans,fm[id]=C(r-l+1);
}
for(int i=1;i<=m;i++) {
LL g=gcd(fz[i],fm[i]);
printf("%lld/%lld\n",fz[i]/g,fm[i]/g);
}
return 0;
}
小B的询问
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
inline int read() {
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x;
}
const int N=50005;
int n,m,k,c[N];
int size,b[N];
int cnt[N];
LL Ans[N],ans;
struct query {
int l,r,id;
bool operator < (const query &x) const {
return ((l-1)/size+1)==((x.l-1)/size+1) ? r<x.r : ((l-1)/size+1)<((x.l-1)/size+1);
}
}q[N];
inline void upd(bool add,int pos) {
int x=c[pos];
add ? ans+=2*(++cnt[x])-1 : ans-=2*(--cnt[x])+1;
//之前cnt[x]=a-1,现在a a^2-(a-1)^2=2*a-1
//之前cnt[x]=a+1,现在a (a+1)^2-a^2=2*a+1
}
int main() {
n=read();m=read();k=read();
size=sqrt(n);
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) upd(1,++r);
while(l>nowl) upd(1,--l);
while(l<nowl) upd(0,l++);
while(r>nowr) upd(0,r--);
Ans[q[i].id]=ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",Ans[i]);
return 0;
}
数列找不同
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
inline int read() {
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x;
}
const int N=100005;
int n,m,k,c[N];
int size,b[N];
int cnt[N];
bool Ans[N];
int ans;
struct query {
int l,r,id;
bool operator < (const query &x) const {
return ((l-1)/size+1)==((x.l-1)/size+1) ? r<x.r : ((l-1)/size+1)<((x.l-1)/size+1);
}
}q[N];
inline void upd(bool add,int pos) {
int x=c[pos];
add ? (ans+=(++cnt[x]==1?1:0)) : (ans-=(--cnt[x]==0?1:0));
}
int main() {
n=read();m=read();
size=sqrt(n);
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) upd(1,++r);
while(l>nowl) upd(1,--l);
while(l<nowl) upd(0,l++);
while(r>nowr) upd(0,r--);
Ans[q[i].id]=ans==(nowr-nowl+1)?1:0;
}
for(int i=1;i<=m;i++)
Ans[i]?printf("Yes\n"):printf("No\n");
return 0;
}
大爷的字符串题
这题就是求*区间众数出现的次数 (-1)
离散化后用个num[] ,表示出现次数为x的数的个数
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=500006;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m,k,c[N],b[N],size,cnt[N],num[N];
inline void Max(int &x,int y){if(x<y)x=y;}
struct node{
int l,r,id;
bool operator < (const node &x) const {
return ((l-1)/size+1)==((x.l-1)/size+1)?r<x.r:((l-1)/size+1)<((x.l-1)/size+1);
}
}q[N];
int ans[N],res;
inline void add(int p) {
p=c[p];
num[cnt[p]]--;
cnt[p]++;
Max(res,cnt[p]);
num[cnt[p]]++;
}
inline void del(int p) {
p=c[p];
if(res==cnt[p]&&num[cnt[p]]==1) res--;
num[cnt[p]]--;
cnt[p]--;
num[cnt[p]]++;
}
int main() {
n=read();m=read();
size=sqrt(n);
for(int i=1;i<=n;i++) c[i]=b[i]=read();
sort(b+1,b+1+n);
int len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
c[i]=lower_bound(b+1,b+1+len,c[i])-b;
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) add(++r);
while(l>nowl) add(--l);
while(l<nowl) del(l++);
while(r>nowr) del(r--);
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++)
printf("%d\n",-ans[i]);
return 0;
}
莫队+值域分块
Gty的二逼妹子序列
[AHOI2013]作业
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100005;
const int M=100005;
int read() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m,c[N],size,cnt[N],bel[N];
void Max(int &x,int y){if(x<y)x=y;}
void Min(int &x,int y){if(x>y)x=y;}
struct node{
int l,r,a,b,id;
bool operator < (const node &x) const {
return bel[l]==bel[x.l]?r<x.r:bel[l]<bel[x.l];
}
}q[N];
int ans[M],ans2[M];
int L[1010],R[1010],sum[1010],sum2[1010];
void add(int x) {if(++cnt[x]==1) sum[bel[x]]++;sum2[bel[x]]++;}
void del(int x) {if(--cnt[x]==0) sum[bel[x]]--;sum2[bel[x]]--;}
int query1(int x,int y) {
int res=0;
if(bel[x]==bel[y]) {
for(int i=x;i<=y;i++) res+=(cnt[i]>0);
return res;
}
for(int i=x;i<=R[bel[x]];i++) res+=(cnt[i]>0);
for(int i=bel[x]+1;i<=bel[y]-1;i++) res+=sum[i];
for(int i=L[bel[y]];i<=y;i++) res+=(cnt[i]>0);
return res;
}
int query2(int x,int y) {
int res=0;
if(bel[x]==bel[y]) {
for(int i=x;i<=y;i++) res+=cnt[i];
return res;
}
for(int i=x;i<=R[bel[x]];i++) res+=cnt[i];
for(int i=bel[x]+1;i<=bel[y]-1;i++) res+=sum2[i];
for(int i=L[bel[y]];i<=y;i++) res+=cnt[i];
return res;
}
int main() {
n=read();m=read();
size=sqrt(n);
memset(L,0x3f,sizeof(L));
for(int i=1;i<=n;i++) {
c[i]=read();
bel[i]=(i-1)/size+1;
Min(L[bel[i]],i);
Max(R[bel[i]],i);
}
for(int i=1;i<=m;i++) {
q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
}
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) add(c[++r]);
while(l>nowl) add(c[--l]);
while(l<nowl) del(c[l++]);
while(r>nowr) del(c[r--]);
ans[q[i].id]=query1(q[i].a,q[i].b);
ans2[q[i].id]=query2(q[i].a,q[i].b);
}
for(int i=1;i<=m;i++)
printf("%d %d\n",ans2[i],ans[i]);
return 0;
}
曼哈顿交易
查区间值出现次数的第k小
值域分块
cnt 出现次数,num[] 值域单点,sum值域块 ——重点query
void add(int x) {
if(cnt[x]) num[cnt[x]]--,sum[bel[cnt[x]]]--,all--;
cnt[x]++;
num[cnt[x]]++;sum[bel[cnt[x]]]++;all++;
}
void del(int x) {
num[cnt[x]]--;sum[bel[cnt[x]]]--;all--;
cnt[x]--;
if(cnt[x]) num[cnt[x]]++,sum[bel[cnt[x]]]++,all++;
}
int query(int k) {
if(all<k) return -1;
for(int i=1;i<=bel[n];i++)
if(k>sum[i]) k-=sum[i];
else {
for(int j=L[i];j<=R[i];j++)
if(k>num[j]) k-=num[j];
else return j;
}
}
带修改的莫队
复杂度$ O(n^{3/5}) $
做法是把修改操作编号,称为"时间戳",而查询操作的时间戳沿用之前最近的修改操作的时间戳
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1000005;
int read() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m,c[N],size,cnt[N],bel[N];
void Max(int &x,int y){if(x<y)x=y;}
void Min(int &x,int y){if(x>y)x=y;}
struct node{
int l,r,tim,id;
bool operator < (const node &x) const {
return (bel[l]^bel[x.l])?bel[l]<bel[x.l]:(bel[r]^bel[x.r])?bel[r]<bel[x.r]:tim<x.tim;
}
}q[N];
int ans[N],res,now;
void add(int x) {if(++cnt[x]==1) res++;}
void del(int x) {if(--cnt[x]==0) res--;}
struct CH{
int pos,val;
}g[N];
void change(int x,int i) {
if(g[x].pos>=q[i].l&&g[x].pos<=q[i].r) {
if(++cnt[g[x].val]==1) res++;
if(--cnt[c[g[x].pos]]==0) res--;
}
swap(g[x].val,c[g[x].pos]);
//交换 ,之后方便再改回来
}
int qm,cm;
int main() {
n=read();m=read();
size=pow(n,2.0/3.0);
for(int i=1;i<=n;i++) {
c[i]=read();
bel[i]=(i-1)/size+1;
}
char op[2];
for(int i=1;i<=m;i++) {
scanf("%s",op);
if(op[0]=='Q')
q[++qm].l=read(),q[qm].r=read(),q[qm].tim=cm,q[qm].id=qm;//时间戳标记为最近的一次修改时间
else
g[++cm].pos=read(),g[cm].val=read();
}
sort(q+1,q+1+qm);
int l=1,r=0;
for(int i=1;i<=qm;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) add(c[++r]);
while(l>nowl) add(c[--l]);
while(l<nowl) del(c[l++]);
while(r>nowr) del(c[r--]);
while(now<q[i].tim) change(++now,i);
while(now>q[i].tim) change(now--,i);
ans[q[i].id]=res;
}
for(int i=1;i<=qm;i++)
printf("%d\n",ans[i]);
return 0;
}
树上莫队
Count on a tree II
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100005;
int read() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m,c[N],b[N],size,cnt[N],bel[N];
struct node{
int l,r,lca,id;
bool operator < (const node &x) const {
return (bel[l]^bel[x.l])?(bel[l]<bel[x.l]):((bel[l]&1)?r<x.r:r>x.r);
}
}q[N];
int ans[N],res;
int hd[N],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
to[++tot]=x;nxt[tot]=hd[y];hd[y]=tot;
}
int f[N][20],dep[N];
int dfn_in[N],dfn_out[N],rev[N],tim;
inline void dfs(int x,int fa,int d) {
dep[x]=d;
f[x][0]=fa;
dfn_in[x]=++tim;rev[tim]=x;
for(int i=1;i<=18&&f[x][i-1];i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=hd[x];i;i=nxt[i]) {
if(to[i]==fa) continue;
dfs(to[i],x,d+1);
}
dfn_out[x]=++tim;rev[tim]=x;
}
inline int LCA(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
for(int i=18;i>=0;i--)
if(dep[f[x][i]]>=dep[y])
x=f[x][i];
if(x==y) return x;
for(int i=18;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
bool vis[N];
inline void work(int x) {
vis[x]?res-=!--cnt[c[x]]:res+=!cnt[c[x]]++;
vis[x]^=1;
}
int main() {
n=read();m=read();
size=sqrt(n);
for(int i=1;i<=n;i++) {
c[i]=b[i]=read();
bel[i]=(i-1)/size+1;
}
sort(b+1,b+1+n);
int len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
c[i]=lower_bound(b+1,b+1+len,c[i])-b;
for(int i=1;i<n;i++) add(read(),read());
dfs(1,0,1);
for(int i=1,a,b,lca;i<=m;i++) {
a=read(),b=read();
lca=LCA(a,b);q[i].id=i;
if(dfn_in[a]>dfn_in[b]) swap(a,b);
if(a==lca) q[i].l=dfn_in[a],q[i].r=dfn_in[b];
else q[i].l=dfn_out[a],q[i].r=dfn_in[b],q[i].lca=lca;
}
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) work(rev[++r]);
while(l>nowl) work(rev[--l]);
while(l<nowl) work(rev[l++]);
while(r>nowr) work(rev[r--]);
if(q[i].lca) work(q[i].lca);
ans[q[i].id]=res;
if(q[i].lca) work(q[i].lca);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
树上带修莫队
糖果公园
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=200005;
#define re register
inline int read() {
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
template<typename F>
inline void write(F x, char ed = '\n')
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar(ed);
}
int n,m,Q;
ll v[N],w[N],c[N],cnt[N];
int bel[N];
struct modui{
int l,r,tim,lca,id;
bool operator < (const modui &x) const {
return (bel[l]^bel[x.l])?bel[l]<bel[x.l]:(bel[r]^bel[x.r])?bel[r]<bel[x.r]:tim<x.tim;
}
}q[N];
struct CH{
int pos;ll val;
}g[N];
int hd[N],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
to[++tot]=x;nxt[tot]=hd[y];hd[y]=tot;
}
int dep[N],dfn_in[N],dfn_out[N],rev[N],dfn_cnt;
int euler[N],dfn[N],len;
inline void dfs(int x,int fa,int d) {
dep[x]=d;
dfn[x]=++len;euler[len]=x;
dfn_in[x]=++dfn_cnt;rev[dfn_cnt]=x;
for(re int i=hd[x];i;i=nxt[i])
if(to[i]!=fa) {
dfs(to[i],x,d+1);
euler[++len]=x;
}
dfn_out[x]=++dfn_cnt;rev[dfn_cnt]=x;
}
int st[20][N],lg[N];
inline int Min(int x,int y) {
return dep[x]<dep[y]?x:y;
}
inline int LCA(int x,int y) {
int le=dfn[x]<dfn[y]?dfn[x]:dfn[y],ri=dfn[x]>dfn[y]?dfn[x]:dfn[y];
int k=lg[ri-le+1];
return Min(st[k][le],st[k][ri-(1<<k)+1]);
}
ll res;
inline void add(int pos) {res+=1ll*v[c[pos]]*w[++cnt[c[pos]]];}
inline void del(int pos) {res-=1ll*v[c[pos]]*w[cnt[c[pos]]--];}
bool vis[N];
inline void work(int pos) {
vis[pos]?del(pos):add(pos);
vis[pos]^=1;
}
inline void change(int x,int i) {
if(vis[g[x].pos]) {
work(g[x].pos);
swap(c[g[x].pos],g[x].val);
work(g[x].pos);
}
if(!vis[g[x].pos])
swap(c[g[x].pos],g[x].val);
}
int qm,cm,now,size;
ll ans[N];
int main() {
n=read();m=read();Q=read();
size=pow(n,2.0/3.0);
for(re int i=1;i<=m;i++) v[i]=read();
for(re int i=1;i<=n;i++) w[i]=read();
for(re int i=2;i<=n;i++)
add(read(),read());
for(re int i=1;i<=n;i++)
c[i]=read(),bel[i]=(i-1)/size+1;;
dfs(1,0,1);
lg[0]=-1;
for(int i=1;i<=len;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=len;i++) st[0][i]=euler[i];
for(int j=1;(1<<j)<=len;j++)
for(int i=1;i+(1<<j)-1<=len;i++)
st[j][i]=Min(st[j-1][i],st[j-1][i+(1<<(j-1))]);
for(re int i=1,ty,a,b;i<=Q;i++) {
ty=read();a=read();b=read();
if(ty) {
int lca=LCA(a,b);
if(dfn_in[a]>dfn_in[b]) swap(a,b);
a==lca?( q[++qm].l=dfn_in[a],q[qm].r=dfn_in[b]):(q[++qm].l=dfn_out[a],q[qm].r=dfn_in[b],q[qm].lca=lca);
q[qm].tim=cm;q[qm].id=qm;
}
if(!ty)
g[++cm].pos=a,g[cm].val=b;
}
sort(q+1,q+1+qm);
int l=1,r=0;
for(re int i=1;i<=qm;i++) {
int nowl=q[i].l,nowr=q[i].r;
while(r<nowr) work(rev[++r]);
while(l>nowl) work(rev[--l]);
while(l<nowl) work(rev[l++]);
while(r>nowr) work(rev[r--]);
while(now<q[i].tim) change(++now,i);
while(now>q[i].tim) change(now--,i);
if(q[i].lca) work(q[i].lca);
ans[q[i].id]=res;
if(q[i].lca) work(q[i].lca);
}
for(re int i=1;i<=qm;i++)
write(ans[i]);
return 0;
}