zoukankan      html  css  js  c++  java
  • 莫队(学习笔记)

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

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

    基础莫队

    小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
    ",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
    ",ans[i]);
        }
        return 0;
    }
    
    

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

  • 相关阅读:
    统计创建对象个数
    动手动脑
    开学第一周心得
    放假第五周总结
    放假第四周总结
    第一周总结
    04-异常处理-动手动脑
    03-继承与多态-动手动脑
    02-类和对象-跟踪类对象创建个数
    02-类和对象-动手动脑
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10304504.html
Copyright © 2011-2022 走看看