莫队
一、LuoGu P3709 大爷的字符串
描述:
给你一个字符串a,每次询问一段区间的贡献
贡献定义:每次从这个区间中随机拿出一个字符x,然后把x从这个区间中删除,你要维护一个集合S
如果S为空,你rp减1
如果S中有一个元素不小于x,则你rp减1,清空S
之后将x插入S
由于你是大爷,平时做过的题考试都会考到,所以每次询问你搞完这段区间的字符之后最多还有多少rp?rp初始为0
询问之间不互相影响。
吐槽:这个题题面十分恶心,看了半天没搞清要干什么,瞟了一下题解发现就是求区间众数,真看语文水平。
思路:
既然是区间众数,那么不就是直接按蒲公英的算法来?
的确可以,只是既然这个题都没有强制在线,我们是不是可以考虑发挥一下?
区间的话考虑莫队。比较套路的,我们可以记录下当前区间内数的出现次数,很容易维护,主要考虑如何统计答案。
这里要用到一个莫队中可能常需要用到的统计答案的方法:对答案分块。可以发现,分块统计答案仍然保证了复杂度的正确性。
在此题中,我们对次数分块,并对于每一个次数 都开一个数组记录 有多少个数出现了这么多次,然后对于每一次移动,相对应的修改次数的数组值就好了。
ps:事实证明我写莫队还是作死了一点,卡了好一会常才过,最慢的一个点999ms。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define RG register #define IL inline using namespace std; IL int gi () { RG int x=0,w=0; char ch=0; while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();} while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return w?-x:x; } const int N=2e5+10; int n,m,len,num,tot,a[N],b[N],c[N],blo[N],cnt[N],ans[N]; struct query {int id,l,r;}qur[N]; IL bool cmp (query A,query B) {return blo[A.l]==blo[B.l]?A.r<B.r:A.l<B.l;} struct BLOCKS {int l,r,cnt,s[510];}B[510]; IL void modify (int pos,int v) { RG int cnum=cnt[a[pos]],bel=blo[cnum]; if (cnum&&(--B[bel].s[cnum-B[bel].l+1]==0)) --B[bel].cnt; cnum=(cnt[a[pos]]+=v),bel=blo[cnum]; if (cnum&&(++B[bel].s[cnum-B[bel].l+1]==1)) ++B[bel].cnt; } IL int Query () { RG int i,dir; for (i=num;i>=1;--i) if (B[i].cnt) {dir=i;break;}; for (i=B[i].r;i;--i) if (B[dir].s[i]) return B[dir].l+i-1; } int main () { RG int i,x,y,Ldir=1,Rdir=0; n=gi(),m=gi(),len=sqrt(n),num=n/len+(n%len!=0); for (i=1;i<=n;++i) b[i]=a[i]=gi(),blo[i]=(i-1)/len+1; sort (b+1,b+n+1); for (i=1;i<=n;++i) if (b[i]!=b[i-1]) c[++tot]=b[i]; for (i=1;i<=n;++i) a[i]=lower_bound(c+1,c+tot+1,a[i])-c; for (i=1;i<num;++i) B[i].l=(i-1)*len+1,B[i].r=len; B[num].l=(num-1)*len+1,B[num].r=n-B[num].l+1; for (i=1;i<=m;++i) qur[i].id=i,qur[i].l=gi(),qur[i].r=gi(); sort (qur+1,qur+m+1,cmp); for (i=1;i<=m;++i) { while (Ldir>qur[i].l) modify(--Ldir,1); while (Ldir<qur[i].l) modify(Ldir++,-1); while (Rdir>qur[i].r) modify(Rdir--,-1); while (Rdir<qur[i].r) modify(++Rdir,1); ans[qur[i].id]=Query(); } for (i=1;i<=m;++i) printf ("%d\n",-ans[i]); return 0; }
二、LuoGu P3674 小清新人渣的本愿
描述:
给你一个序列a,长度为n,有m次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ,这三个操作分别为操作1,2,3
选出的这两个数可以是同一个位置的数。
思路:
对于1操作:我们可以维护一个数组now,记录每个数是否在当前区间中出现,对于询问只要考虑把这个01数组整体左移x位,和原数组与(&)一下, 至少有一位上为1就说明满足。
可是这样的话复杂度是线性的,所以我们需要用bitset优化。
对于2操作:考虑和上面的一样,但比较灵活一点。我们考虑如何构造:假设存在a+b=x,那么存在(N-a)-(N-x)=b。所以我们考虑另外维护一个bitset记录(N-这个数)是否出现,记为fnow。
那么对于询问,只需把fnow右移(N-x)位,再和now与(&)一下就好了。
对于3操作:乍一看不能和上面一样维护,但是考虑是乘积的形式,所以O(√n)的枚举一下约数就好了。
所以这个复杂度有点玄学,感觉有点不对,但是跑的挺快的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define RG register #define IL inline using namespace std; IL int gi () { RG int x=0,w=0; char ch=0; while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();} while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return w?-x:x; } const int N=1e5; bitset<N+10> now,fnow; int n,m,len,a[N+10],cnt[N+10],ans[N+10]; struct query {int id,typ,l,r,v,bel;}qur[N+10]; IL bool cmp (query A,query B) {return A.bel==B.bel?A.r<B.r:A.l<B.l;} IL void add (int col) {if (++cnt[col]==1) now[col]=fnow[N-col]=1;} IL void del (int col) {if (--cnt[col]==0) now[col]=fnow[N-col]=0;} int main () { RG int i,j,ty,x,y,v,Ldir=1,Rdir=0; n=gi(),m=gi(),len=sqrt(n); for (i=1;i<=n;++i) a[i]=gi(); for (i=1;i<=m;++i) ty=gi(),x=gi(),y=gi(),v=gi(),qur[i]=(query){i,ty,x,y,v,(x-1)/len+1}; sort (qur+1,qur+m+1,cmp); for (i=1;i<=m;++i) { while (Ldir>qur[i].l) add(a[--Ldir]); while (Ldir<qur[i].l) del(a[Ldir++]); while (Rdir>qur[i].r) del(a[Rdir--]); while (Rdir<qur[i].r) add(a[++Rdir]); if (qur[i].typ==1) if ((now&(now<<qur[i].v)).any()) ans[qur[i].id]=1; if (qur[i].typ==2) if ((now&(fnow>>(N-qur[i].v))).any()) ans[qur[i].id]=1; if (qur[i].typ==3) { for (j=1;j*j<=qur[i].v;++j) if (qur[i].v%j==0&&now[j]&&now[qur[i].v/j]) {ans[qur[i].id]=1;break;} } } for (i=1;i<=m;++i) puts(ans[i]?"hana":"bi"); return 0; }
WC2013 糖果公园
描述:懒得蒯了 LuoGuP4074
思路:
树上待修莫队板子题。
和普通待修莫队不一样的地方:
1、询问分块方式。一般有两种分块方式:一种是dfs一遍,用栈记录,辅助分块。另一种是利用dfs序来分块。我比较喜欢前者。
2、统计答案时需要注意一些东西。假设:原本是a,b两点,且对于原来的有a,b间路径除LCA(a,b)外其他点都被标记为经过,现在是c,d两点。
主要讨论一下LCA的问题:
1°、如果LCA(a,b)还在c,d的路径上,那么必然至少一个点还在以LCA(a,b)为根的子树中,所以只会有至多一个点经过LCA(a,b),标记为经过。
而LCA(c,d)不会有点经过,此时间c,d路径除LCA(c,d)外其他点都被标记为经过,符合假设。
2°、如果LCA(a,b)不在c,d的路径上,那么两个点都会经过LCA(a,b),相当于没操作依然没被标记,不影响,符合假设。
综上可知,我们每次移动后需对LCA处单独处理一下,统计完答案后还需还原。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define RG register #define IL inline #define LL long long using namespace std; IL int gi () { RG int x=0,w=0; char ch=0; while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();} while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return w?-x:x; } const int N=1e5+10; LL Ans,ans[N]; int top,sta[N]; int tot,head[N]; int Tim,dfn[N],dep[N],f[N][21]; int n,m,q,qnum,a[N],V[N],W[N],C[N]; int len,bnum,md[N][2],blo[N],vis[N],cnt[N]; struct Edge {int next,to;}e[N<<1]; IL void make (int from,int to) { e[++tot]=(Edge){head[from],to}; head[from]=tot; } struct Query {int id,u,v,T;}qur[N]; IL bool cmp (Query A,Query B) { if (blo[A.u]==blo[B.u]) return (blo[A.v]==blo[B.v])?A.T<B.T:dfn[A.v]<dfn[B.v]; return dfn[A.u]<dfn[B.u]; } void dfs (int x,int fx) { RG int i,y,tp=top; f[x][0]=fx,dep[x]=dep[fx]+1,dfn[x]=++Tim; for (i=head[x];i;i=e[i].next) { if ((y=e[i].to)==fx) continue; dfs (y,x); if (top-tp>len) { ++bnum; while (top>tp) blo[sta[top--]]=bnum; } } sta[++top]=x; } IL void get_f () { for (RG int i=1;i<=20;++i) for (RG int j=1;j<=n;++j) f[j][i]=f[f[j][i-1]][i-1]; } IL int get_lca (int x,int y) { RG int i; if (dep[x]<dep[y]) swap(x,y); for (i=20;i>=0;--i) if (dep[f[x][i]]>=dep[y]) x=f[x][i]; if (x==y) return x; for (i=20;i>=0;--i) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } IL void add (int col) {Ans+=(LL)V[col]*W[++cnt[col]];} IL void del (int col) {Ans-=(LL)V[col]*W[cnt[col]--];} IL void remove (int pos) {(vis[pos]^=1)?add(C[pos]):del(C[pos]);} IL void modify (int T) { if (vis[md[T][0]]) add(md[T][1]),del(C[md[T][0]]); swap (md[T][1],C[md[T][0]]); } IL void GO (int x,int y) { if (dep[x]<dep[y]) swap (x,y); while (dep[x]>dep[y]) remove(x),x=f[x][0]; while (x!=y) remove(x),remove(y),x=f[x][0],y=f[y][0]; } int main () { RG int i,op,x,y,T=0,Tdir=0,u=1,v=1,LCA; n=gi(),m=gi(),q=gi(),len=pow(n,2.0/3.0); for (i=1;i<=m;++i) V[i]=gi(); for (i=1;i<=n;++i) W[i]=gi(); for (i=1;i<n;++i) { x=gi(),y=gi(); make (x,y),make (y,x); } for (i=1;i<=n;++i) C[i]=gi(); for (i=1;i<=q;++i) { op=gi(),x=gi(),y=gi(); if (op==1) qur[++qnum]=(Query){qnum,x,y,T}; else md[++T][0]=x,md[T][1]=y; } dfs (1,1),get_f (); while (top) blo[sta[top--]]=bnum+1;++bnum; sort (qur+1,qur+qnum+1,cmp); for (i=1;i<=qnum;++i) { while (Tdir>qur[i].T) modify(Tdir--); while (Tdir<qur[i].T) modify(++Tdir); while (u!=qur[i].u) GO(u,qur[i].u),u=qur[i].u; while (v!=qur[i].v) GO(v,qur[i].v),v=qur[i].v; remove(LCA=get_lca(u,v)),ans[qur[i].id]=Ans,remove(LCA); } for (i=1;i<=qnum;++i) printf ("%lld\n",ans[i]); return 0; }