莫队(学习笔记)

推荐一篇莫队算法的博客!!!

莫队其实是一种将所有询问离线的算法,它巧妙地利用了每个询问之间的关系,从而优化时间复杂度.另外,莫队基于分块的思想.

基础莫队

小Z的袜子

题目描述

小Z有N只袜子,从1到N编号,每只袜子有一个颜色\(C_i\),有M次询问,每次询问[L,R],回答区间[L,R]内,小Z有多大的概率抽到两只颜色相同的袜子.

然而数据中有L=R的情况,请特判这种情况,输出0/1。

输出包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。

这里谈一下概率的求法:

假设有a,b,c......n中颜色,则

(分子分母的算法类比n只球队,两两进行一场比赛的总场数)

分子为:

\(a*(a-1)/2+b*(b-1)/2+c*(c-1)/2+......\)

分母为:

\((R-L+1)*(R-L)/2\)

化简得:

分子:\(a^2+b^2+c^2+...-(a+b+c+...)\)

=\(a^2+b^2+c^2+...-(R-L+1)\)

分母:\((R-L+1)*(R-L)\)

int n,m,t;
int color[50005],place[50005];
long long ans,sum[50005];
struct mo{
    int l,r,id;
    long long a,b;
}qu[50005];
bool cmp1(mo x,mo y){
    return place[x.l]==place[y.l]?x.r<y.r:x.l<y.l;
}
bool cmp2(mo x,mo y){
    return x.id<y.id;
}
long long SUM(long long x){return x*x;}
void turn(int x,int y){
    ans-=SUM(sum[color[x]]);
    sum[color[x]]+=y;
    ans+=SUM(sum[color[x]]);
}
//这里ans记录的是上面提到的累加和,不是整个概率分数
//以y=-1为例:
//对于颜色color[x],它在ans中由sum[color[x]]的平方
//变为sum[color[x]-1]的平方,为了方便处理
//直接先减掉原来的sum[color[x]]的平方
//然后sum[color[x]]-1变为当前的贡献
//再把其平方值加入ans中
long long GCD(long long x,long long y){
    if(y==0)return x;
    return GCD(y,x%y);
}
int main(){
    n=read();m=read();
    t=sqrt(n);//分块
    for(int i=1;i<=n;i++){
		place[i]=i/t+1;
    }//place数组记录第i只袜子所属的块
    for(int i=1;i<=n;i++){
		color[i]=read();
    }//color数组记录第i只袜子的颜色
    for(int i=1;i<=m;i++){
		qu[i].l=read();
		qu[i].r=read();
		qu[i].id=i;
    }
//把m个询问一起用结构体记录下来,转为离线
//别忘了要把每个询问编号,以便于最后按照顺序输出
    sort(qu+1,qu+m+1,cmp1);
//把每个询问按照左端点从小到大排序
//莫队的核心开始了!!!
    int l=1,r=0;
    for(int i=1;i<=m;i++){
		while(l<qu[i].l){turn(l,-1);l++;}
//l指针小于当前询问区间的左端点,
//就要把l位置上的颜色减1,指针位置向右挪.
		while(l>qu[i].l){turn(l-1,1);l--;}
		while(r<qu[i].r){turn(r+1,1);r++;}
		while(r>qu[i].r){turn(r,-1);r--;}
//想象一下l,r两个指针跳来跳去的名场面
		if(qu[i].l==qu[i].r){
	    	qu[i].a=0;qu[i].b=1;
	    	continue;
		}//特判l=r的情况
		qu[i].a=ans-(qu[i].r-qu[i].l+1);
		qu[i].b=1LL*(qu[i].r-qu[i].l+1)*(qu[i].r-qu[i].l);
		long long gcd=GCD(qu[i].a,qu[i].b);
		qu[i].a/=gcd;qu[i].b/=gcd;
    }
    sort(qu+1,qu+m+1,cmp2);
//恢复原来询问的顺序输出结果
    for(int i=1;i<=m;i++){
		printf("%lld/%lld\n",qu[i].a,qu[i].b);
    }
    return 0;
}

带修莫队

数颜色

多次区间询问,询问区间\([l,r]\)中不同颜色的种类数。可以单点修改颜色。

带修莫队在普通莫队的基础上多了第三个指针t,记录时间,这个时间是指修改的时间(次数)

借助我们记录的修改时间,每一次询问不仅要让l,r两个指针跳来跳去(跳到正确的区间内),还要保证当前已经修改到此次询问发生的时间(询问和修改是交错发生的,所以我们要查询此次问题的结果,首先要保证当前修改的时间/次数指针t跳到了当前询问应在的查询次数时)

int n,m,unit,num,t,l=1,r,Time,Ans;
int color[50005],now[50005],place[50005];
int sum[1000005],ans[50005];
struct query{
    int l,r,tim,id;
}qu[50005];
//一个结构体记录问题(询问区间),记录信息有:
//左端点,右端点,询问时间(带修莫队的特色),编号
struct change{
    int pos,New,Old;
}ch[50005];
//这个结构体是为修改操作量身打造的
//pos位置编号,New修改后的颜色,Old修改前的颜色
bool cmp(query a,query b){
    return place[a.l]==place[b.l]?(place[a.r]==place[b.r]?a.tim<b.tim:a.r<b.r):a.l<b.l;
}按照三个关键字l>r>tim排序('>'在这里指优先于)
void turn(int col,int d){
    sum[col]+=d;
    if(d>0)Ans+=sum[col]==1;
//等价于if(d>0&&sum[col]==1)Ans+=1;
//如果d>0(即d=1),sum[col]+1之后才等于1,
//说明颜色col之前对答案还未产生贡献,此时Ans+1
    if(d<0)Ans-=sum[col]==0;//同理
}
void going(int x,int col){
    if(l<=x&&x<=r){turn(col,1);turn(color[x],-1);}
//如果这一次修改的位置在询问区间内
//就把修改后的颜色加1,修改前(被修改)的颜色减1
    color[x]=col;
//更新x位置上的颜色
}
int main(){
    n=read();m=read();
    unit=pow(n,0.666666);
//本题中这样分块最好,这个是需要数学能力的
    for(int i=1;i<=n;i++){
		color[i]=read();
		now[i]=color[i];
		place[i]=i/unit+1;
    }
//新增了一个now数组来记录当前的颜色
    for(int i=1;i<=m;i++){
		char op;int a,b;
		cin>>op;a=read();b=read();
		if(op=='Q'){qu[++num]=(query){a,b,Time,num};}
//询问操作:num询问编号,在第Time次修改之后的询问
		if(op=='R'){ch[++Time]=(change){a,b,now[a]};now[a]=b;}
//修改操作:Time修改次数,a位置修改为颜色b
//now[a]修改为当前的颜色b
    }
    sort(qu+1,qu+num+1,cmp);
//对这num个询问排序,开始莫队
    for(int i=1;i<=num;i++){
		while(t<qu[i].tim){going(ch[t+1].pos,ch[t+1].New);t++;}
//如果当前的修改次数t在 此次询问前已修改次数tim 之前
		while(t>qu[i].tim){going(ch[t].pos,ch[t].Old);t--;}
		while(l<qu[i].l){turn(color[l],-1);l++;}
		while(l>qu[i].l){turn(color[l-1],1);l--;}
		while(r<qu[i].r){turn(color[r+1],1);r++;}
		while(r>qu[i].r){turn(color[r],-1);r--;}
		ans[qu[i].id]=Ans;
    }
    for(int i=1;i<=num;i++){
		printf("%d\n",ans[i]);
    }
    return 0;
}

副本(尚未开启):树上带修莫队(太蒻了!!!)

posted on 2019-01-22 16:23  PPXppx  阅读(94)  评论(0编辑  收藏  举报