题目

题目大意

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


思考历程

第一次看到这道题,立马想到了树套树之类的二位数据结构,发现不行。(就算可以也很难打……)

然后我就想到了莫队!

其实这题的莫队是很显然的。我们用莫队的方法来搞,用一个数据结构来维护目前的答案。

所以我就打出来了时间复杂度为O(mnlg⁡n)O(m \sqrt n \lg n)O(mn​lgn)的做法。

还挺好打的。

交上去之后,我发现,诶,怎么运行这么久?难道是被卡了?

后来看到分数之后……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)O\left(m \sqrt n\right)O(mn​),少了一个lg⁡n\lg nlgn

第一种是XZB大佬首创的莫队加桶维护的方法:

莫队的部分是一模一样的,重点是桶。

我们知道莫队排序的时候,按照左端点所在的块为第一关键字,以右端点为第二关键字。

显然,当左端点所在块一样的时候,右端点是递增的。

我们在处理的时候,用桶记录一下当前块后面的信息。

然后,对于在块内的那一部分区间,我们就暴力计算。计算了之后合并两边的信息,求出这一问的答案。

现在有一个问题,怎么合并呢?

这个问题困扰了我一段时间。然后,我发现,反正这个时间复杂度就这样了,所以暴力一点没有关系。

我们先不要理在块中的部分,先统计好块后面的信息,记录这时的带权众数。

然后将块内的数加进桶中,做完之后这时的带权众数就是答案。

最后,我们将桶还原,就是将原来属于这个块内的全部剔除,带权众数还是之前的那个带权众数。

这个是我自己脑补出来的想法,可能其他人还有更加优秀的方法吧。

一直怎么做下去,如果一个块处理完了,就将桶清空,然后继续处理下一个块。

时间复杂度是显然的。

然后还有一种做法叫作分块,我还没有打过,只是思想理解了而已。

我们可以用和上面有点类似的思想:将一段区间内的东西用桶存起来,存下此时的答案,然后再试着将区间外的东西暴力加进去,得出目前的答案,最后还原。

我们可以分成n\sqrt nn​个块,对于每个块,我们将这个块之前的所有东西放在一个桶中。(就像是前缀和)

然后,我们对于每两个块之间的部分,预处理出它们的带权众数。(用一个n∗n\sqrt n *\sqrt nn​∗n​的一个数组来存就好,预处理的时候直接枚举从哪个块开始,然后往后面扫,时间O(nn)O(n \sqrt n)O(nn​))

询问一个区间的时候,这个区间被拆成一个大块和两个散块。对于大块,我们已经预处理除它的带权众数,并且我们通过相减的方式得出一个新的桶。对于散块,我们将里面的元素暴力加进桶中,得出最终的带权众数。

然后你会惊奇地发现,如果真的是这么做,那肯定TLE。

为什么?其实耗费时间的就在一个地方:我们将两个桶相减得出一个新的桶,实际上没有必要。

设后面的桶为aaa,前面的桶为bbb,那么你可以新建一个桶ccc。ccc一开始是全零的。

然后,在后面加数的时候,“新桶”相当于是a−b+ca-b+ca−b+c。在加某个数xxx的时候,我们只需要修改cxc_xcx​,用ax−bx+cxa_x-b_x+c_xax​−bx​+cx​来统计答案。做完了之后,将这些数从ccc中一一减去,然后ccc就清零了(如果直接清零是会TLE的)。

这个时间复杂和上面的一样,不过感觉上面的好打一些。


代码

O(mnlg⁡n)O(m \sqrt n \lg n)O(mn​lgn)的水法

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\n",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(m\sqrt n)O(mn​)的莫队加桶做法

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

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


总结

如果见到一些用lg⁡\lglg做法不好维护的东西,那就试一下分块和莫队。

从这题当中,我们也得到了一个用来优化莫队的思想,就是将块后和块中的分别考虑,有时会有非常好的成果。

最后就是,卡常技巧很重要,说不定你可以用次解来AC这道题。不要像YMQ一样天天吸臭氧。

[JZOJ4763] 【NOIP2016提高A组模拟9.7】旷野大计算的更多相关文章

  1. JZOJ 4732. 【NOIP2016提高A组模拟8.23】函数

    4732. [NOIP2016提高A组模拟8.23]函数 (Standard IO) Time Limits: 1500 ms  Memory Limits: 262144 KB  Detailed ...

  2. 【JZOJ4763】【NOIP2016提高A组模拟9.7】旷野大计算

    题目描述 输入 输出 样例输入 5 5 9 8 7 8 9 1 2 3 4 4 4 1 4 2 4 样例输出 9 8 8 16 16 数据范围 解法 离线莫队做法 考虑使用莫队,但由于在删数的时候难以 ...

  3. 【NOIP2016提高A组模拟9.14】数列编辑器

    题目 分析 比赛上,没有注意到询问只询问光标前面,于是只打了个暴力. 因为询问只询问光标前面,首先,当光标向后每移动到一个位置,顺便将这个位置的前缀和,和最大前缀和求出来. 总之,模拟 #includ ...

  4. 【NOIP2016提高A组模拟9.24】总结

    第一题纯模拟,结果那个出题人脑子似乎进水了,空间限制开了1G!!! 导致我捉摸了半天为什么空间要开那么大,最后只能得出上面的结论. 第二题是个矩阵快速幂,比赛上我没把递推式求出来,但是根据各种乱搞,得 ...

  5. 【JZOJ4746】【NOIP2016提高A组模拟9.3】树塔狂想曲

    题目描述 相信大家都在长训班学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和.走的规则是:(i,j)号点只能走向(i+1,j)或者(i+1,j+1).如下图是一个数塔,映射到该数塔上行 ...

  6. 【JZOJ4745】【NOIP2016提高A组模拟9.3】看电影

    题目描述 听说NOIP2016大家都考得不错,于是CCF奖励省常中了 K 张变形金刚5的电影票奖励OI队的同学去看电影.可是省常中OI队的同学们共有 N(N >= K)人.于是机智的你想到了一个 ...

  7. 【JZOJ4803】【NOIP2016提高A组模拟9.28】求导

    题目描述 输入 输出 样例输入 2x^2+3x+1 样例输出 4x+3 数据范围 样例解释 求导的意思: 多项式是由若干个单项式构成的 单项式的一般形式是ax^b,其中ab都是常数,x是自变量 对于单 ...

  8. 【JZOJ4787】【NOIP2016提高A组模拟9.17】数格子

    题目描述 输入 输出 样例输入 1 10000 3 10000 5 10000 0 0 样例输出 1 11 95 数据范围 每个测试点数据组数不超过10组 解法 状态压缩动态规划. 设f[i][j]表 ...

  9. [jzoj 4668] [NOIP2016提高A组模拟7.19] 腐败 解题报告(质数分类+慢速乘)

    题目链接: http://172.16.0.132/senior/#main/show/4668 题目: 题解: 考虑把A数组里的每个元素分解质因数,对于每个质因数开一个vector存一下包含这个质因 ...

随机推荐

  1. LeetCode 196. Delete Duplicate Emails (删除重复的电子邮箱)

    题目标签: 题目给了我们一个 email 的表格,让我们删除重复的. 建立Person p1,Person p2,当email 相同时,而且 p1 id 要大于 p2 id 时候,删除这一行. Jav ...

  2. PAT_A1053#Path of Equal Weight

    Source: PAT A1053 Path of Equal Weight (30 分) Description: Given a non-empty tree with root R, and w ...

  3. day 89 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件   本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...

  4. Codeforces 1154B Make Them Equal

    题目链接:http://codeforces.com/problemset/problem/1154/B 题意:给定数组,可以给任意的的元素加上D 或者 减去D,如果能 使数组元素都相等,输出最小的D ...

  5. linux格式化磁盘

    linux格式化磁盘 查看系统磁盘情况 [root@db02 ~]# fdisk -l #查看当前系统上所有存储设备(包括挂在和没挂载的)  注:如果没有管理员权限是看不见磁盘的,因为fdisk默认读 ...

  6. js实现图片资源、blob、base64的各种场景转换

    文件转babase64 function getImgToBase64(url,callback){//将图片转换为Base64 var canvas = document.createElement ...

  7. Linux 实用指令(9)--进程管理

    目录 进程管理 1 进程的基本介绍 2 显示系统执行的进程 2.1 说明: 2.2 ps指令详解 2.3 应用实例 3 终止进程kill和killall 3.1 介绍 3.2 基本语法 3.3 常用选 ...

  8. YARN设计思路

  9. Linux sed命令实现替换文本内容

    /root/data/code-s3201/publish_codex/deploy/db.properties db.properties中的 1.0.0.6 替换为 1.0.0.7 sed -i ...

  10. cpu子系统(优化)

    如果业务已经在线上,你要优化,第一步如何做 首先进行服务器数据采集,和性能监测分析 一:使用cacti,nagios,zabbix 等监控工具 二:使用linux 自带的一些监控指令:vmstat,i ...