[JZOJ4763] 【NOIP2016提高A组模拟9.7】旷野大计算
题目
题目大意
给你一个数列,有很多个询问,询问一段区间内,某个数乘它的出现次数的最大值,也就是带权众数。
思考历程
第一次看到这道题,立马想到了树套树之类的二位数据结构,发现不行。(就算可以也很难打……)
然后我就想到了莫队!
其实这题的莫队是很显然的。我们用莫队的方法来搞,用一个数据结构来维护目前的答案。
所以我就打出来了时间复杂度为O(mnlgn)O(m \sqrt n \lg n)O(mnlgn)的做法。
还挺好打的。
交上去之后,我发现,诶,怎么运行这么久?难道是被卡了?
后来看到分数之后……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),少了一个lgn\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(mnlgn)O(m \sqrt n \lg n)O(mnlgn)的水法
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】旷野大计算的更多相关文章
- JZOJ 4732. 【NOIP2016提高A组模拟8.23】函数
4732. [NOIP2016提高A组模拟8.23]函数 (Standard IO) Time Limits: 1500 ms Memory Limits: 262144 KB Detailed ...
- 【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 数据范围 解法 离线莫队做法 考虑使用莫队,但由于在删数的时候难以 ...
- 【NOIP2016提高A组模拟9.14】数列编辑器
题目 分析 比赛上,没有注意到询问只询问光标前面,于是只打了个暴力. 因为询问只询问光标前面,首先,当光标向后每移动到一个位置,顺便将这个位置的前缀和,和最大前缀和求出来. 总之,模拟 #includ ...
- 【NOIP2016提高A组模拟9.24】总结
第一题纯模拟,结果那个出题人脑子似乎进水了,空间限制开了1G!!! 导致我捉摸了半天为什么空间要开那么大,最后只能得出上面的结论. 第二题是个矩阵快速幂,比赛上我没把递推式求出来,但是根据各种乱搞,得 ...
- 【JZOJ4746】【NOIP2016提高A组模拟9.3】树塔狂想曲
题目描述 相信大家都在长训班学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和.走的规则是:(i,j)号点只能走向(i+1,j)或者(i+1,j+1).如下图是一个数塔,映射到该数塔上行 ...
- 【JZOJ4745】【NOIP2016提高A组模拟9.3】看电影
题目描述 听说NOIP2016大家都考得不错,于是CCF奖励省常中了 K 张变形金刚5的电影票奖励OI队的同学去看电影.可是省常中OI队的同学们共有 N(N >= K)人.于是机智的你想到了一个 ...
- 【JZOJ4803】【NOIP2016提高A组模拟9.28】求导
题目描述 输入 输出 样例输入 2x^2+3x+1 样例输出 4x+3 数据范围 样例解释 求导的意思: 多项式是由若干个单项式构成的 单项式的一般形式是ax^b,其中ab都是常数,x是自变量 对于单 ...
- 【JZOJ4787】【NOIP2016提高A组模拟9.17】数格子
题目描述 输入 输出 样例输入 1 10000 3 10000 5 10000 0 0 样例输出 1 11 95 数据范围 每个测试点数据组数不超过10组 解法 状态压缩动态规划. 设f[i][j]表 ...
- [jzoj 4668] [NOIP2016提高A组模拟7.19] 腐败 解题报告(质数分类+慢速乘)
题目链接: http://172.16.0.132/senior/#main/show/4668 题目: 题解: 考虑把A数组里的每个元素分解质因数,对于每个质因数开一个vector存一下包含这个质因 ...
随机推荐
- PMP-49个过程快速导览
启动过程组 名正言顺 人鬼佛神 01 -4.1制定项目章程 -授权PM,启动项目 ● 编写一份正式批准项目并授权项目经理在项目活动中使用组织资源的文件的过程 ● 明确项目与组织战略目标之间的联系,确立 ...
- ashx后门[转]
https://www.cnblogs.com/Fluorescence-tjy/p/9855828.html 一.标准ASPX一句话木马 .NET平台下的一句话木马则百年不变,最常见的当属下面这句 ...
- 实时查询系统架构:spark流式处理+HBase+solr/ES查询
最近要做一个实时查询系统,初步协商后系统的框架 1.流式计算:数据都给spark 计算后放回HBase 2.查询:查询采用HBase+Solr/ES
- 22-MySQL-Ubuntu-备份与恢复
案例: 将数据库jing_dong_1的数据库备份,然后恢复到数据库jing_dong_2; 步骤如下: 1.备份 注意: mysqldump 不是 mysql; 尖括号的方向; python.sql ...
- 【牛客提高训练营5B】旅游
题目 吉老师的题时过一年还是不会做 从\(1\)号点出发经过每条边至少一次并且还要回到\(1\)号点,这跟欧拉回路的条件非常像,但是欧拉回路的实际上是"经过每一条边恰好一次并且回到出发点&q ...
- 再次封装ajax函数,统一入口
根据API写网页的时候,每个页面都需要ajax请求,每次都写一大堆请求,配置什么的太麻烦,于是打算封装一个ajax函数,统一调用: 开始时是使用return返回ajax,如下: function cr ...
- SpringCloud学习笔记《---06 Config 分布式配置中心---》基础篇
- JS对象 指定分隔符连接数组元素join() join()方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。
指定分隔符连接数组元素join() join()方法用于把数组中的所有元素放入一个字符串.元素是通过指定的分隔符进行分隔的. 语法: arrayObject.join(分隔符) 参数说明: 注意:返回 ...
- 关于VSCode的一些常用插件和一些常用设置
常用插件: .Beautify :格式化 html ,js,css .Bracket Pair Colorizer :给括号加上不同的颜色,便于区分不同的区块,使用者可以定义不同括号类型和不同颜色 . ...
- Navicat Premium下载、安装、破解
Navicat Premium 是一套数据库管理工具,让你以单一程序同時连接到 MySQL.MariaDB.SQL Server.SQLite.Oracle 和 PostgreSQL 数据库. 此外, ...