莫队入门例题之持久化莫队:2120: 数颜色

166 篇文章 0 订阅

 

·述大意:

       多个区间询问,询问[l,r]中颜色的种类数。可以单点修改颜色。

·分析:

莫队可以修改?那不是爆炸了吗。

这类爆炸的问题被称为带修莫队(可持久化莫队)。

按照美妙类比思想,可以引入一个“修改时间”,表示当前询问是发生在前Time个修改操作后的。也就是说,在进行莫队算法时,看看当前的询问和时间指针(第三个指针,别忘了l,r)是否相符,然后进行时光倒流或者时光推移操作来保证答案正确性。

·Sort的构造。仅靠原来的sort关键字会使得枚举每个询问都可能因为时间指针移动的缘故要移动n次,总共就n2次,那还不如写暴力。

·为了防止这样的事情发生,再加入第三关键字Tim:

image

·如何理解时间复杂度?

首先,R和Tim的关系就像L和R的关系一样:只有在前者处于同块时,后者才会得到排序的恩赐,否则sort会去满足前者,使得后者开始乱跳。

依旧像上文那样:枚举m个答案,就一个m了。设分块大小为unit。

分类讨论:

①对于l指针,依旧是O(unit*n)

②对于r指针,依旧是O(n*n/unit)

③对于T指针(即Time):

    类比r时间复杂度的计算。我们要寻找有多少个单调段(一个单调段下来最多移动n次)。上文提到,当且仅当两个询问l在同块,r也在同块时,才会对可怜的Tim进行排序。局势明朗。对于每一个l的块,里面r最坏情况下占据了所有的块,所以最坏情况下:有n/unit个l的块,每个l的块中会有n/unit个r的块,此时,在一个r块里,就会出现有序的Tim。所以Tim的单调段个数为:(n/unit)*(n/unit)。每个单调段最多移动n次。

所以:O((n/unit)2*n)

三个指针汇总:O(unit*n+n2/unit+(n/unit)2*n)

image

例题如下:

 

传送门: 2120: 数颜色

个人代码:

 

 

第一次修改:莫队必须分块。d代码就不做调整了   文章末尾有分块实现的代码

 

#include<bits/stdc++.h>
using namespace std;
struct Query{int l,r,tim,id;}q[10006];//存放查询,查询的区间 lr 查询时的时间,第几个查询 
struct update{int pos,New,Old;}u[10005];//存放修改,修改的位置,修改成什么,修改之前是什么 
int n,m,xx,yy,t,color[100005],s[10005],Ans,Time,now[10005],ans[100005],l=1,r=0,T;
//n是区间长度,m是操作次数,xx和yy是每次操作用的,t是查询计数器,color是当前答案区间内的颜色,s是存放数据的,Ans是当前答案
//Time是修改计数器(当前时间) ,now是当前的颜色,ans是答案数组, lr是上一次查询的区间初始化为10,T是上一次查询的时间 
char c;//问题类型是什么 
bool cmp(Query a,Query b){return a.l==b.l?(a.r==b.r?a.tim<b.tim:a.r<b.r):a.l<b.l;} 
void revise(int x,int d){
	color[x]+=d;//为答案数组里改变这个颜色的数量 
	if(d<0)Ans-=color[x]==0;//如果是从有到无 
	else Ans+=color[x]==1;//如果是从无到有 
} 
void going(int x,int d){
	if(l<=x&&x<=r)revise(d,1),revise(s[x],-1);//如果是上一次区间内的,才修改答案(Ans是答案,用来不断修改的答案) 
	s[x]=d;//修改这个值 
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&s[i]); 
		now[i]=s[i];
	}//输入 
	for(int i=1;i<=m;i++){
		scanf(" %c%d%d",&c,&xx,&yy);
		if(c=='Q')q[++t]=(Query){xx,yy,Time,t};//t是计数器,也就是查询xx-yy 在Time时的情况 
		else u[++Time]=(update){xx,yy,now[xx]},now[xx]=yy;//这一次修改之前x的颜色是now[xx],修改成yy,并将now更新 
	}
	sort(q+1,q+t+1,cmp);//根据左节点 右节点 时间戳进行排序 
	for(int i=1;i<=t;i++){
		while(T<q[i].tim)going(u[T+1].pos,u[T+1].New),T++;//如果当下是过去,去未来 
		while(T>q[i].tim)going(u[T].pos,u[T].Old),T--; //如果当下是未来  回到过去
		
		while(l<q[i].l)revise(s[l],-1),l++;//当前l小于区间 
		while(l>q[i].l)revise(s[l-1],1),l--;//。。 。 
		while(r<q[i].r)revise(s[r+1],1),r++;//。。 。 
		while(r>q[i].r)revise(s[r],-1),r--;//。。。 
		
		ans[q[i].id]=Ans;//存放的是答案 
	}
	for(int i=1;i<=t;i++)printf("%d\n",ans[i]);
	return 0;
}

大佬代码:

#include<stdio.h>
#include<algorithm>
#include<math.h>
#define go(i,a,b) for(int i=a;i<=b;i++)
using namespace std;const int N=10003;
struct Query{int l,r,Tim,ID;}q[N];
struct Change{int pos,New,Old;}c[N];
int n,m,s[N],color[N*100],t,Time,now[N],unit,Be[N],ans[N],Ans,l=1,r,T;
bool cmp(Query a,Query b)
{
    return Be[a.l]==Be[b.l]?(Be[a.r]==Be[b.r]?a.Tim<b.Tim:a.r<b.r):a.l<b.l;
}
void revise(int x,int d){color[x]+=d;if(d>0)Ans+=color[x]==1;if(d<0)Ans-=color[x]==0;}
void going(int x,int d){if(l<=x&&x<=r)revise(d,1),revise(s[x],-1);s[x]=d;}
int main(){
    scanf("%d%d",&n,&m);unit=pow(n,0.666666);
    go(i,1,n)scanf("%d",&s[i]),now[i]=s[i],Be[i]=i/unit+1;
    go(i,1,m){char sign;int x,y;scanf(" %c %d%d",&sign,&x,&y);
        if(sign=='Q')q[++t]=(Query){x,y,Time,t};
        if(sign=='R')c[++Time]=(Change){x,y,now[x]},now[x]=y;
    }
    sort(q+1,q+t+1,cmp);go(i,1,t)
    {
        while(T<q[i].Tim)going(c[T+1].pos,c[T+1].New),T++;
        while(T>q[i].Tim)going(c[T].pos,c[T].Old),T--;
        
        while(l<q[i].l)revise(s[l],-1),l++;
        while(l>q[i].l)revise(s[l-1],1),l--;
        while(r<q[i].r)revise(s[r+1],1),r++;
        while(r>q[i].r)revise(s[r],-1),r--;
        
        ans[q[i].ID]=Ans;
    }
    go(i,1,t)printf("%d\n",ans[i]);return 0;
}//Paul_Guderian

大佬博客:https://www.cnblogs.com/Paul-Guderian/p/6933799.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值