zoukankan      html  css  js  c++  java
  • 「刷题笔记」莫队

    莫队算法——从入门到黑题 by WAMonster
    (感谢yspm的推荐

    板子

    对位置进行分块,记录询问每个端点所属的块,排序时,左端点按所属的块排序,右端点按原位置排序
    淦发现之前在博客里发出了“莫队左端点移动O(n)”的暴论
    排序后,左端点移动的次数是(O(nsqrt{n}))的,
    而右端点在每个相同左端点中递增,移动的次数是(O(nsqrt{n})),所以算法总复杂度为(O(nsqrt{n}))
    还有一些奇怪的优化比如奇偶性排序,但是并不会证只能先背过了……

    SP3267 DQUERY - D-query

    code:
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 30005
    #define Q 200005
    #define C 1000005
    
    ll n,a[N],bel[N];
    ll q;
    struct query
    {
    	ll a,b,id;
    	ll aa,bb;
    	bool operator<(const query &y)const
    	{
    		return aa==y.aa?b<y.b:aa<y.aa;
    	}
    }qq[Q];
    
    inline ll read()
    {
    	ll f=0,s=0; char c=getchar();
    	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
    	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
    	return f?-s:s;
    }
    
    ll cnt[C];
    ll an=0;
    ll ans[Q];
    
    void add(ll x)
    {
    	if(!cnt[a[x]])an++;
    	cnt[a[x]]++;
    }
    
    void del(ll x)
    {
    	cnt[a[x]]--;
    	if(!cnt[a[x]])an--;
    }
    
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)a[i]=read();
    	ll S=sqrt(n)+1;
    	for(int i=1;i<=n;i++)bel[i]=(i-1)/S+1;
    	q=read();
    	for(int i=1;i<=q;i++)
    	{
    		qq[i].a=read(),qq[i].b=read(),qq[i].id=i;
    		qq[i].aa=bel[qq[i].a],qq[i].bb=bel[qq[i].b];
    	}
    	sort(qq+1,qq+q+1);
    	ll l=1,r=0;
    	for(int i=1;i<=q;i++)
    	{
    		ll nl=qq[i].a,nr=qq[i].b;
    		while(nl>l)del(l++);
    		while(nl<l)add(--l);
    		while(nr>r)add(++r);
    		while(nr<r)del(r--);
    		ans[qq[i].id]=an;
    	}
    	for(int i=1;i<=q;i++)printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    luogu P2709 小B的询问

    一个挺有用的统计平方和的套路:从(a^2)((a+1)^2),即为加上(2a+1)
    所以只需要统计每种颜色当前区间内的出现次数就好

    二维莫队

    蔬菜

    原理和一维差不多,只不过是相当于区间变成了矩形,所以移动的时候要对一个区间进行加加减减
    用二维莫队就可以转化成和小B的询问套路相同的题

    带修莫队

    P1903 [国家集训队]数颜色 / 维护队列

    与普通莫队的区别在于支持修改,当然莫队需要离线
    若强制在线就得考虑别的方法了,但是如果支持修改且可以离线有时就可以考虑带修莫队
    为了支持修改,带修莫队需要在普通莫队的基础上加一个时间戳,时间戳记录的是每个询问上一个修改操作的时间,
    把修改操作单独储存后,就可以通过时间戳的加减来实现对于某个区间答案的时间变换
    感性理解:就是由原来的((l+1,r))((l-1,r))((l,r+1))((l,r-1))四个方向的转移
    变成了((l+1,r,t))((l-1,r,t))((l,r+1,t))((l,r-1,t))((l,r,t+1))((l,r,t-1))六个方向的转移
    转移的时候需要记录当前值以备转移回去,可以直接把当前值与修改操作中的修改值交换,这样回来的时候就能直接换回去
    奇偶性排序直接12s->5s是什么操作

    code:
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 133350
    #define C 1000005
    
    ll n,m;
    ll a[N];
    ll cnt[C],ans[N];
    
    struct query
    {
    	ll l,r,t,id;
    	ll lb,rb;
    	bool operator<(const query &y)const
    	{
    		return (lb^y.lb)?lb<y.lb:(rb^y.rb)?rb<y.rb:t<y.t;
    	}
    }q[N];
    ll qcn=0;
    
    struct update
    {
    	ll x,v;
    }c[N];
    
    inline ll read()
    {
    	ll f=0,s=0; char c=getchar();
    	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
    	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
    	return f?-s:s;
    }
    
    ll ti=0,an=0;
    char ch[2];
    
    void add(ll x=0,ll c=0)
    {
    	if(x)
    	{
    		if(cnt[a[x]]==0)an++;
    		cnt[a[x]]++;
    	}
    	else if(c)
    	{
    		if(cnt[c]==0)an++;
    		cnt[c]++;
    	}
    }
    
    void del(ll x=0,ll c=0)
    {
    	if(x)
    	{
    		cnt[a[x]]--;
    		if(cnt[a[x]]==0)an--;
    	}
    	else if(c)
    	{
    		cnt[c]--;
    		if(cnt[c]==0)an--;
    	}
    }
    
    int main()
    {
    	n=read(); m=read();
    	ll S=pow(n,3.0/4.0);
    	for(int i=1;i<=n;i++)a[i]=read();
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%s",ch);
    		if(ch[0]=='Q')
    		{
    			q[++qcn].l=read(),q[qcn].r=read(),q[qcn].t=ti,q[qcn].id=qcn;
    			q[qcn].lb=(q[qcn].l-1)/S+1,q[qcn].rb=(q[qcn].r-1)/S+1;
    		}
    		else if(ch[0]=='R')
    		{
    			c[++ti].x=read(),c[ti].v=read();
    		}
    	}
    	sort(q+1,q+qcn+1);
    	ll l=1,r=0,t=0;
    	for(int i=1;i<=qcn;i++)
    	{
    		ll nl=q[i].l,nr=q[i].r,lt=q[i].t;
    		while(l<nl)del(l++,0);
    		while(l>nl)add(--l,0);
    		while(r<nr)add(++r,0);
    		while(r>nr)del(r--,0);
    		while(t>lt)
    		{
    			if(nl<=c[t].x&&c[t].x<=nr)del(c[t].x,0),add(0,c[t].v);
    			swap(a[c[t].x],c[t].v);
    			t--;
    		}
    		while(t<lt)
    		{
    			t++;
    			if(nl<=c[t].x&&c[t].x<=nr)del(c[t].x,0),add(0,c[t].v);
    			swap(a[c[t].x],c[t].v);
    		}
    		ans[q[i].id]=an;
    	}
    	for(int i=1;i<=qcn;i++)printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    树上莫队

    SP10707 COT2

    考虑怎样把树上的点转化成一个容易用莫队处理的序列,那么可以使用欧拉序
    考虑路径((u,v)(dep_u<dep_v))上的点在欧拉序里是怎么表现的
    首先,忽略在一段欧拉序中出现两次的点,这些点不在所求路径上
    记点(k)在欧拉序中第一次出现在(fi_k),最后一次出现在(la_k),那么:

    • 如果(lca(u,v)=u),那么所求区间为([fi_u,fi_v])
    • 否则,所求区间为([la_u,fi_v]),但这个区间不包含他们的lca,这时lca要单独处理

    这样,就可以用莫队处理树上的问题了

    code:
    #include<bits/stdc++.h>
    using namespace std;
    #define ll int
    #define ull unsigned long long
    #define N 200005
    #define gc() (p1==p2?(p2=buf+fread(p1=buf,1,1<<20,stdin),p1==p2?EOF:*p1++):*p1++)
    char buf[1<<20],*p1,*p2;
    
    ll n,m;
    ll w[N],b[N],tot=0;
    ll t1,t2;
    
    ll v[N];
    ll h[N],nx[N],tt=0;
    
    void add(ll a,ll b){
    	v[++tt]=b;
    	nx[tt]=h[a];
    	h[a]=tt;
    }
    
    ll bel[N];
    struct query{
    	ll l,r,id,lc;
    	ll lb,rb;
    	bool operator<(const query &b)const{
    		return (bel[l]^bel[b.l])?(bel[l]<bel[b.l]):((bel[l]&1)?r<b.r:r>b.r);
    	}
    }q[N];
    
    ll a[N*2],fi[N],la[N],cnt=0;
    ll fa[N][21],dep[N];
    void dfs(ll u,ll f){
    	a[++cnt]=u;
    	fa[u][0]=f;
    	dep[u]=dep[f]+1;
    	fi[u]=cnt;
    	for(int i=1;i<=20;i++){
    		fa[u][i]=fa[fa[u][i-1]][i-1];
    	}
    	for(int i=h[u];i;i=nx[i]){
    		if(v[i]==f)continue;
    		dfs(v[i],u);
    	}
    	a[++cnt]=u;
    	la[u]=cnt;
    }
    
    inline int lca(int u,int v){
    	if(dep[u]<dep[v])swap(u,v);
    	for(int i=20;i>=0;--i)if(dep[u]-(1<<i)>=dep[v])u=fa[u][i];
    	if(u==v)return u;
    	for(int i=20;i>=0;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
    	return fa[u][0];
    }
    
    bool vis[N];
    ll cn[N];
    ll an=0,ans[N];
    inline void work(int pos){
    	vis[pos]?an-=!--cn[w[pos]]:an+=!cn[w[pos]]++;
    	vis[pos]^=1;
    }
    
    inline ll read(){
    	ll f=0,s=0; char c=gc();
    	while(c>'9'||c<'0')f=(c=='-'),c=gc();
    	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=gc();
    	return f?-s:s;
    }
    
    int main(){
    	n=read(), m=read();
    	for(int i=1;i<=n;i++)w[i]=read(),b[++tot]=w[i];
    	sort(b+1,b+tot+1); tot=unique(b+1,b+tot+1)-b-1;
    	for(int i=1;i<=n;i++)w[i]=lower_bound(b+1,b+tot+1,w[i])-b;//cout<<w[i]<<' ';cout<<endl;
    	for(int i=1;i<n;i++)t1=read(),t2=read(),add(t1,t2),add(t2,t1);
    	dfs(1,0);
    	ll S=sqrt(cnt),bnum=ceil((double)cnt/S);
    	for(int i=1;i<=bnum;++i)for(int j=S*(i-1)+1;j<=i*S;++j){
    		bel[j]=i;
    	}
    	for(int i=1;i<=m;i++){
    		q[i].l=read(),q[i].r=read(),q[i].id=i;
    		ll tlc=lca(q[i].l,q[i].r);
    		if(fi[q[i].l]>fi[q[i].r])swap(q[i].l,q[i].r);
    		if(tlc==q[i].l)q[i].l=fi[q[i].l],q[i].r=fi[q[i].r];
    		else q[i].l=la[q[i].l],q[i].r=fi[q[i].r],q[i].lc=tlc;
    	}
    	sort(q+1,q+m+1);
    	ll l=1,r=0;
    	for(int i=1;i<=m;i++){
    		ll nl=q[i].l,nr=q[i].r;
    		while(l<nl)work(a[l++]);
    		while(l>nl)work(a[--l]);
    		while(r<nr)work(a[++r]);
    		while(r>nr)work(a[r--]);
    		if(q[i].lc)work(q[i].lc);
    		ans[q[i].id]=an;
    		if(q[i].lc)work(q[i].lc);
    	}
    	for(int i=1;i<=m;i++)printf("%d
    ",ans[i]);
    	return 0;
    }
    

    需要注意的:

    • 莫队中的分块,在点的编号较小,询问较多时,先统一求出每个数所属的块,不用每个询问单独算
    • lca倍增预处理(lg)数组会变慢,最好直接根据数据范围定下上界(21,31这种的)

    要是这两点出了岔子程序效率就会极其低……

    回滚莫队

    有时候题目中并不是很好实现莫队移动区间时的“删除”操作,这时可以用回滚莫队来绕开删除的不便
    具体的实现是把询问按左端点所属的块排序,块内按右端点编号排序,左右端点在同一块就暴力算,否则右端点从当前块右端出发向右走(这个移动排序后是单调的),左端点每次从当前块右端向左走,处理完每个询问后再把左端点移到块右端,并把答案恢复到上一次左端点走之前的状态
    具体细节见代码

    洛谷 P5906 【模板】回滚莫队&不删除莫队
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define N 200005
    
    ll n,m,a[N],b[N],S,tot=0;
    ll bel[N];
    
    struct query{
    	ll l,r,id;
    	inline bool operator<(const query &b)const{
    		return (bel[l]==bel[b.l])?(r<b.r):(bel[l]<bel[b.l]);
    	}
    }q[N];
    ll pre[N],lst[N],ans[N];
    ll st[N],tp=0;
    
    inline ll work(ll l,ll r){ ll an=0;
    	for(int i=l;i<=r;i++){
    		if(!pre[a[i]])pre[a[i]]=i,st[++tp]=a[i];
    		an=max(an,i-pre[a[i]]);
    	}
    	while(tp)pre[st[tp]]=0,tp--;
    	return an;
    }
    
    inline ll read(){
    	ll f=0,s=0; char c=getchar();
    	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
    	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
    	return f?-s:s;
    }
    
    int main(){
    	n=read(); for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
    	m=read(); for(int i=1;i<=m;i++)q[i].l=read(), q[i].r=read(), q[i].id=i;
    	S=sqrt(n)+1; for(int i=1;i<=n;i++)bel[i]=(i-1)/S+1;
    	sort(b+1,b+tot+1); tot=unique(b+1,b+tot+1)-b-1;
    	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
    	sort(q+1,q+m+1);
    	for(int i=1,j=1;i<=S;i++){ ll br=i*S, l=br+1, r=br, an=0;
    		for(;bel[q[j].l]==i;j++){ ll ql=q[j].l, qr=q[j].r;
    			if(bel[ql]==bel[qr]){ ans[q[j].id]=work(ql,qr); continue; }
    			while(r<qr){
    				r++; lst[a[r]]=r;
    				if(!pre[a[r]])pre[a[r]]=r,st[++tp]=a[r]; an=max(an,r-pre[a[r]]);
    			}
    			ll tmp=an;
    			while(l>ql){
    				l--; if(!lst[a[l]])lst[a[l]]=l,st[++tp]=a[l];
    				an=max(an,lst[a[l]]-l);
    			}
    			ans[q[j].id]=an;
    			while(l<=br){
    				if(lst[a[l]]==l)lst[a[l]]=0; l++;
    			}
    			an=tmp;
    		}
    		while(tp)pre[st[tp]]=lst[st[tp]]=0,tp--;
    	}
    	for(int i=1;i<=m;i++)printf("%lld
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    20个命令行工具监控 Linux 系统性能【转载】
    机器学习及部分NLP相关网上的代码资源整理
    职场中脱颖而出的成长秘诀【转载】
    美好的诗词
    其实这是一种生活方式【转载】
    10个非常有趣的Linux命令【转载】
    如何摆脱工具类【转载】
    在线公开课 | 如何基于模型训练平台快速打造AI能力?
    企业应用架构演化探讨:从微服务到Service Mesh
    生命的价值——大型科技公司的时间“减”史
  • 原文地址:https://www.cnblogs.com/zzzuozhe-gjy/p/14017090.html
Copyright © 2011-2022 走看看