zoukankan      html  css  js  c++  java
  • [JZOJ4763] 【NOIP2016提高A组模拟9.7】旷野大计算

    题目

    在这里插入图片描述

    题目大意

    给你一个数列,有很多个询问,询问一段区间内,某个数乘它的出现次数的最大值,也就是带权众数。


    思考历程

    第一次看到这道题,立马想到了树套树之类的二位数据结构,发现不行。(就算可以也很难打……)
    然后我就想到了莫队!
    其实这题的莫队是很显然的。我们用莫队的方法来搞,用一个数据结构来维护目前的答案。
    所以我就打出来了时间复杂度为O(mnlgn)O(m sqrt n lg n)的做法。
    还挺好打的。
    交上去之后,我发现,诶,怎么运行这么久?难道是被卡了?
    后来看到分数之后……WA,0分。
    什么鬼?
    然后我就惊奇地发现这题要开long long……
    开了之后,诶,40分?
    怎么还是这么低的分数(某YMQ用这种方法AC了这题)?
    检查程序,发现自己莫队的排序相当于没有排序。
    我们知道朴素的莫队做法就是将左端点所在的块为第一关键字,以右端点为第二关键字来排的。
    比较函数一般设为:be[x.l]<be[y.l] || be[x.l]==be[y.l] && x.r<y.r
    然后我把||打成了&&
    感觉自己心态爆炸……这样子和没有排序有什么区别?
    改过来之后,60分,嗯,题解分数。
    但我不甘心,因为还有两个点是WA!
    经过调试之后,我发现是数组越界了,再改改就80分了。
    可是还没到100分,因为这不是正解。但YMQ已经卡到100分了,我才不信这个邪。
    所以呢,我又卡了一下:
    首先将普通的线段树转化为zkw线段树(因为只有单点修改和整体查询,所以还是比较简单的),然后用宏来打了max函数,因为自带的那个是挺慢的。
    然后就卡过了!比YMQ快!看看YMQ的程序,嘿嘿,还开了O3……
    YMQ日常吸臭氧……


    正解

    目前我知道的正解有两种,时间复杂度都是O(mn)Oleft(m sqrt n ight),少了一个lgnlg n
    第一种是XZB大佬首创的莫队加桶维护的方法:
    莫队的部分是一模一样的,重点是桶。
    我们知道莫队排序的时候,按照左端点所在的块为第一关键字,以右端点为第二关键字。
    显然,当左端点所在块一样的时候,右端点是递增的。
    我们在处理的时候,用桶记录一下当前块后面的信息。
    然后,对于在块内的那一部分区间,我们就暴力计算。计算了之后合并两边的信息,求出这一问的答案。
    现在有一个问题,怎么合并呢?
    这个问题困扰了我一段时间。然后,我发现,反正这个时间复杂度就这样了,所以暴力一点没有关系。
    我们先不要理在块中的部分,先统计好块后面的信息,记录这时的带权众数。
    然后将块内的数加进桶中,做完之后这时的带权众数就是答案。
    最后,我们将桶还原,就是将原来属于这个块内的全部剔除,带权众数还是之前的那个带权众数。
    这个是我自己脑补出来的想法,可能其他人还有更加优秀的方法吧。
    一直怎么做下去,如果一个块处理完了,就将桶清空,然后继续处理下一个块。
    时间复杂度是显然的。

    然后还有一种做法叫作分块,我还没有打过,只是思想理解了而已。
    我们可以用和上面有点类似的思想:将一段区间内的东西用桶存起来,存下此时的答案,然后再试着将区间外的东西暴力加进去,得出目前的答案,最后还原。
    我们可以分成nsqrt n个块,对于每个块,我们将这个块之前的所有东西放在一个桶中。(就像是前缀和)
    然后,我们对于每两个块之间的部分,预处理出它们的带权众数。(用一个nnsqrt n *sqrt n的一个数组来存就好,预处理的时候直接枚举从哪个块开始,然后往后面扫,时间O(nn)O(n sqrt n)
    询问一个区间的时候,这个区间被拆成一个大块和两个散块。对于大块,我们已经预处理除它的带权众数,并且我们通过相减的方式得出一个新的桶。对于散块,我们将里面的元素暴力加进桶中,得出最终的带权众数。
    然后你会惊奇地发现,如果真的是这么做,那肯定TLE。
    为什么?其实耗费时间的就在一个地方:我们将两个桶相减得出一个新的桶,实际上没有必要。
    设后面的桶为aa,前面的桶为bb,那么你可以新建一个桶cccc一开始是全零的。
    然后,在后面加数的时候,“新桶”相当于是ab+ca-b+c。在加某个数xx的时候,我们只需要修改cxc_x,用axbx+cxa_x-b_x+c_x来统计答案。做完了之后,将这些数从cc中一一减去,然后cc就清零了(如果直接清零是会TLE的)。
    这个时间复杂和上面的一样,不过感觉上面的好打一些。


    代码

    O(mnlgn)O(m sqrt n lg n)的水法

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #define N 252144
    int n,m,col;
    int a[N+1],p[N+1],b[N+1];//a表示原数组,b表示离散化后的数组
    inline bool cmpp(const int x,const int y){
    	return a[x]<a[y];
    }
    int K;
    int be[N+1];//表示所在的块
    struct Oper{
    	int l,r;
    	int num;	
    } o[N+1];
    inline bool cmp(const Oper &x,const Oper &y){
    	return be[x.l]<be[y.l] || be[x.l]==be[y.l] && x.r<y.r;//比较函数千万不要打错了……我当时就是在这里GG的
    }
    long long t[N*2+1];
    int M;
    inline void add(int,int);
    long long ans[N+1];
    int main(){
    	scanf("%d%d",&n,&m);
    	K=sqrt(n);
    	for (int i=1;i<=n;++i)
    		scanf("%d",&a[i]),p[i]=i;
    	//以下是离散化
    	sort(p+1,p+n+1,cmpp);
    	for (int i=1,bef=0;i<=n;++i){
    		if (a[p[i]]!=bef)
    			bef=a[p[i]],col++;
    		b[p[i]]=col;
    	}
    	for (M=1;M<col;M<<=1);
    	for (int i=1;i<=m;++i)
    		scanf("%d%d",&o[i].l,&o[i].r),o[i].num=i;
    	for (int i=1;i*K<=n;++i)
    		for (int j=0;j<K && i*K+j<=n;++j)
    			be[i*K+j]=i;
    	//以下是莫队
    	sort(o+1,o+m+1,cmp);
    	int l=1,r=0;
    	for (int i=1;i<=m;++i){
    		for (;r<o[i].r;r++)
    			add(r+1,1);
    		for (;l>o[i].l;l--)
    			add(l-1,1);
    		for (;r>o[i].r;r--)
    			add(r,-1);
    		for (;l<o[i].l;l++)
    			add(l,-1);
    		ans[o[i].num]=t[1];
    	}
    	for (int i=1;i<=m;++i)
    		printf("%lld
    ",ans[i]);
    	return 0;
    }
    #define my_max(x,y) (((x)>(y))?(x):(y))
    inline void add(int x,int c){//zkw线段树中的
    	int k=b[x]+M;
    	t[k]+=c*a[x];
    	for (k>>=1;k;k>>=1)
    		t[k]=my_max(t[k<<1],t[k<<1|1]);
    }
    

    O(mn)O(msqrt n)的莫队加桶做法

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #define N 252144
    int n,m,col;
    int a[N+1],p[N+1],b[N+1];
    inline bool cmpp(const int x,const int y){
    	return a[x]<a[y];
    }
    int K;
    int be[N+1],rig[N+1];
    struct Oper{
    	int l,r;
    	int num;	
    } o[N+1];
    inline bool cmp(const Oper &x,const Oper &y){
    	return be[x.l]<be[y.l] || be[x.l]==be[y.l] && x.r<y.r;
    }
    long long buc[N+1],rmx;
    long long ans[N+1];
    #define MAX(a,b) (((a)>(b))?(a):(b))
    int main(){
    //	freopen("in.txt","r",stdin);
    //	freopen("out.txt","w",stdout);
    	scanf("%d%d",&n,&m);
    	K=sqrt(n);
    	for (int i=1;i<=n;++i)
    		scanf("%d",&a[i]),p[i]=i;
    	sort(p+1,p+n+1,cmpp);
    	for (int i=1,bef=0;i<=n;++i){
    		if (a[p[i]]!=bef)
    			bef=a[p[i]],col++;
    		b[p[i]]=col;
    	}
    	for (int i=1;i<=m;++i)
    		scanf("%d%d",&o[i].l,&o[i].r),o[i].num=i;
    	for (int i=0;1+i*K<=n;++i)
    		for (int j=1;j<=K && i*K+j<=n;++j)
    			be[i*K+j]=i+1;
    	be[0]=0;
    	for (int i=1;i<=n;++i)
    		rig[be[i]]=i;
    	sort(o+1,o+m+1,cmp);	
    	for (int i=1,r=0;i<=m;++i){
    		if (be[o[i-1].l]!=be[o[i].l]){
    			memset(buc,0,sizeof buc);
    			rmx=0;
    			r=rig[be[o[i].l]]+1;
    			for (;r>o[i].r;--r)
    				buc[b[r-1]]-=a[r-1];//防止左端点和右端点在同一个块中的情况
    		}
    		for (;r<=o[i].r;++r)
    			buc[b[r]]+=a[r],rmx=MAX(rmx,buc[b[r]]);//右端点往外延伸
    		long long tmx=rmx;//由于等一下要还原,所以这个值要暂时记录
    		for (int l=rig[be[o[i].l]];l>=o[i].l;--l)
    			buc[b[l]]+=a[l],tmx=MAX(tmx,buc[b[l]]);//将块内的暴力记录在桶中
    		ans[o[i].num]=tmx;
    		for (int l=rig[be[o[i].l]];l>=o[i].l;--l)//还原
    			buc[b[l]]-=a[l];
    	}
    	for (int i=1;i<=m;++i)
    		printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    其实这题的数据很水……我一开始的正解程序并没有判断左右端点在同一个块中的特殊情况,可我还是AC了。


    总结

    如果见到一些用lglg做法不好维护的东西,那就试一下分块和莫队。
    从这题当中,我们也得到了一个用来优化莫队的思想,就是将块后和块中的分别考虑,有时会有非常好的成果。
    最后就是,卡常技巧很重要,说不定你可以用次解来AC这道题。不要像YMQ一样天天吸臭氧。

  • 相关阅读:
    .NET Core技术研究-通过Roslyn代码分析技术规范提升代码质量
    ASP.NET Core技术研究-全面认识Web服务器Kestrel
    .NET Core技术研究-主机Host
    ASP.NET Core技术研究-探秘依赖注入框架
    ASP.NET Core技术研究-探秘Host主机启动过程
    .NET Core技术研究-中间件的由来和使用
    深入浅出腾讯BERT推理模型--TurboTransformers
    深入浅出PyTorch(算子篇)
    深入浅出Transformer
    生产者消费者问题总结
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145246.html
Copyright © 2011-2022 走看看