题目又臭又长,但其实题意很简单。

给出一个长度为\(N\)的序列与\(Q\)个询问,每个询问都对应原序列中的一个区间。对于每个查询的区间,设数\(X_{i}\)在此区间出现的次数为\(Sum_{X_{i}}\),我们需要求出对于当前区间\(X_{i}*Sum_{X_{i}}\)的最大值。

数据范围:\(1\leq N,Q\leq10^{5},1\leq X_{I}\leq1 0^{9}\)


众所周知,对于没有修改的区间查询问题且数据范围在\(1e5\)的题目,我们首先就可以考虑使用莫队来解决,事实上这道题也是可以用莫队来解决的,不过需要一点变形。

对每个询问进行分块排序后,使用莫队算法。将原序列的数进行离散化,记每个数出现的次数为\(Cnt_{x}\),当前莫队的左右下标为\(l\)和\(r\),当前最优答案为\(sum\)。

我们发现当\(l\)减少和\(r\)增加时的情况很好更新答案(也就是原莫队的\(add\)操作,与之对应的删除操作也就是\(del\)),只需要\(Cnt_{r}++\),然后求一个最大值\(max(sum,Cnt_{r})\)。

但是\(del\)操作就很难实现。如果删除的数对应的是最大值,也就是在\(sum=Cnt_{r}*X_{r}\)中,\(X_{r}--\)了,那么我们就不能保证当前的\(sum\)是最大的。考场上笔者想到的解决方法是维护一个次大值,但可以发现如果要维护次大值那还要维护一个第3大值。。。

考场上笔者是使用的线段树来解决这个问题,维护每个\(Cnt_{i}*X_{i}\)的最大值,时间复杂度对应的是\(O(n \sqrt{n}log_{n})\)。尽管理论上可以过,卡常之后也确实可以过,但并不完美因为我不会卡常。这里是常数比较大的笔者的40分套线段树代码。

#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
#include <cmath>
using namespace std; #define LL long long
const int N=100020; struct node {
LL p,sum;
node() {};
node(LL S,LL P) { p=P; sum=S; }
}; LL ans[N],maxx[N<<4],M2[N],n,m,A[N],T,cnt;
map<LL,LL> M1; struct Qu {
int l,r,p;
bool operator < (const Qu &nxt) {//分块排序
if(l/T+1 != nxt.l/T+1) return l/T+1<nxt.l/T+1;
else return r<nxt.r;
}
}tzy[N]; void insert(int i,int l,int r,int x,int t) {//线段树维护
if(l==r) {
maxx[i]+=t;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) insert(i<<1,l,mid,x,t);
else insert(i<<1|1,mid+1,r,x,t);
maxx[i]=max(maxx[i<<1],maxx[i<<1|1]);
} void add(int now) {
insert(1,1,cnt,A[now],M2[A[now]]);
} void del(int now) {
insert(1,1,cnt,A[now],-M2[A[now]]);
} int main() {
cin>>n>>m; T=sqrt(n);
for(int i=1;i<=n;i++) {
scanf("%lld",&A[i]);
if(!M1[A[i]]) {
M1[A[i]]=++cnt;
M2[cnt]=A[i];
}
A[i]=M1[A[i]];
}
// cout<<endl<<cnt<<endl;
for(int i=1;i<=m;i++) {
scanf("%d%d",&tzy[i].l,&tzy[i].r);
tzy[i].p=i;
}
sort(tzy+1,tzy+1+m);
int l=1,r=1; insert(1,1,cnt,A[1],M2[A[1]]);
// for(int i=1;i<=m;i++) { cout<<tzy[i].l<<' '<<tzy[i].r<<endl;}
for(int i=1;i<=m;i++) {
while(r<tzy[i].r) add(++r);
while(l>tzy[i].l) add(--l);
while(r>tzy[i].r) del(r--);
while(l<tzy[i].l) del(l++);
ans[tzy[i].p]=maxx[1];
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}

另一种思路其实是值得我们学习的。如果\(del\)操作不好实现,那为什么我们不能跳出优化\(del\)操作的框框,而是想办法将问题转化为只使用\(add\)操作呢?

于是便有了莫队算法的变形:回滚莫队,适用于不好维护\(del\)操作的情况,且它的时间复杂度也十分优美\(O(n \sqrt {n})\)(不像上一个又臭又长)。

回滚莫队可以说是将分块的思想用到了极致。我们将问题一个块一个块的处理。因为我们是将询问的\(r\)从小到大排序,所以对于同一块的处理,可以保证\(r\)的转移是\(O(n)\)的,且只涉及到\(add\)操作。

但是\(l\)就不好处理了,因为它的排列相较于\(r\)的排列是无序的。但因为是分块排列,我们可以保证从当前块的右端点\(R_{i}\),到\(l\),最多只有\(\sqrt {n}\)步。

于是我们便可以考虑这样一种策略。每次处理都将\(l\)移到\(R_{i}\),这样可以保证\(l\)和\(r\)都只有\(add\)操作。当前询问处理完后,只需要将\(l\)“滚”回来。因为\(l\)的转移最多为\(\sqrt {n}\)次,\(r\)转移最多为\(n\)次,所以复杂度为\(O(n \sqrt{n})\)。

思路讲完了,但还有种特殊情况需要处理,当\(L_{i}\leq l,r\leq R_{i}\)时,也就是当前询问左右端点都处于同一个块时运用上述方法并不好处理。因为可以保证\(r-l \leq \sqrt{n}\),所以只需要暴力跑一边即可。

细节比较多,详情见代码。

#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std; #define LL long long
const int N=100020; LL ans[N],B[N],q,cnt[N],block[N],L[N],R[N],Cntl,n,sum,m,p,A[N],T; struct Qu {
int l,r,p;
bool operator < (const Qu &nxt) {
return block[l]==block[nxt.l] ? r<nxt.r:l<nxt.l;
}
}tzy[N]; void divide() {
for(int i=1;i<=q;i++) L[i]=R[i-1]+1,R[i]=L[i]+T-1;
if(R[q]<n) L[q+1]=R[q]+1,R[q+1]=n,q++;
for(int i=1;i<=n;i++) block[i]=(i-1)/T+1;
} void add(int x) {
cnt[A[x]]++;
if(cnt[A[x]]*B[A[x]]>sum) sum=cnt[A[x]]*B[A[x]];
} LL solve(int l,int r) {
static int c[N];
LL solu=0;
for(int i=l;i<=r;i++) ++c[A[i]];
for(int i=l;i<=r;i++) solu=max(solu,c[A[i]]*B[A[i]]);
for(int i=l;i<=r;i++) --c[A[i]];
return solu;
} int main() {
cin>>n>>m; T=sqrt(n); q=n/T;
for(int i=1;i<=n;i++) scanf("%lld",&A[i]),B[i]=A[i];
for(int i=1;i<=m;i++) scanf("%d%d",&tzy[i].l,&tzy[i].r),tzy[i].p=i;
sort(B+1,B+1+n);
p=unique(B+1,B+1+n)-B-1;
// cout<<endl<<cnt<<endl;
for(int i=1;i<=n;i++) A[i]=lower_bound(B+1,B+1+p,A[i])-B;
divide();
sort(tzy+1,tzy+1+m); for(int i=1,l,r,j=1;i<=q;i++) {
r=R[i]; sum=0;
memset(cnt,0,sizeof(cnt));
while(block[tzy[j].l]==i) {
l=R[i]+1;
if(tzy[j].r-tzy[j].l<=T)
ans[tzy[j].p]=solve(tzy[j].l,tzy[j].r),++j;
else {
while(r<tzy[j].r) add(++r);
LL tmp=sum;
while(l>tzy[j].l) add(--l);
ans[tzy[j].p]=sum;
sum=tmp;
while(l<=R[i]) --cnt[A[l++]];
++j;
}
}
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}

考试还有一个每个节点为\(4*4\)的矩阵的题没有写。对于我这种一个月一更的作者,写个博客要一上午的人。。。还早呢

「JOISC 2014 Day1」历史研究 --- 回滚莫队的更多相关文章

  1. 「JOISC 2014 Day1」 历史研究

    「JOISC 2014 Day1」 历史研究 Solution 子任务2 暴力,用\(cnt\)记录每种权值出现次数. 子任务3 这不是一个尺取吗... 然后用multiset维护当前的区间,动态加, ...

  2. 「题解」「JOISC 2014 Day1」历史研究

    目录 题目 考场思考 思路分析及标程 题目 点这里 考场思考 大概是标准的莫队吧,离散之后来一个线段树加莫队就可以了. 时间复杂度 \(\mathcal O(n\sqrt n\log n)\) . 然 ...

  3. bzoj4241/AT1219 历史研究(回滚莫队)

    bzoj4241/AT1219 历史研究(回滚莫队) bzoj它爆炸了. luogu 题解时间 我怎么又在做水题. 就是区间带乘数权众数. 经典回滚莫队,一般对于延长区间简单而缩短区间难的莫队题可以考 ...

  4. BZOJ4241:历史研究(回滚莫队)

    Description IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. ...

  5. BZOJ.4241.历史研究(回滚莫队 分块)

    题目链接 \(Description\) 长度为n的数列,m次询问,每次询问一段区间最大的 \(A_i*tm_i\) (重要度*出现次数) \(Solution\) 好像可以用莫队做,但是取max的操 ...

  6. BZOJ4241历史研究——回滚莫队

    题目描述 IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. 日记中记录了连 ...

  7. 「JOISC 2014 Day1」巴士走读

    「JOISC 2014 Day1」巴士走读 将询问离线下来. 从终点出发到起点. 由于在每个点(除了终点)的时间被过来的边固定,因此如果一个点不被新的边更新,是不会发生变化的. 因此可以按照时间顺序, ...

  8. LOJ#6504. 「雅礼集训 2018 Day5」Convex(回滚莫队)

    题面 传送门 题解 因为并不强制在线,我们可以考虑莫队 然而莫队的时候有个问题,删除很简单,除去它和前驱后继的贡献即可.但是插入的话却要找到前驱后继再插入,非常麻烦 那么我们把它变成只删除的回滚莫队就 ...

  9. loj#6517. 「雅礼集训 2018 Day11」字符串(回滚莫队)

    传送门 模拟赛的时候纯暴力竟然骗了\(70\)分-- 首先对于一堆\(g\)怎么计算概率应该很好想,用总的区间数减去不合法的区间数就行了,简而言之对\(g\)排个序,每一段长为\(d\)的连续序列的区 ...

随机推荐

  1. JVM 调优 —— GC 长时间停顿问题及解决方法

    零. 简介 垃圾收集器长时间停顿,表现在 Web 页面上可能是页面响应码 500 之类的服务器错误问题,如果是个支付过程可能会导致支付失败,将造成公司的直接经济损失,程序员要尽量避免或者说减少此类情况 ...

  2. Android ListView批量选择(全选、反选、全不选)

    APP的开发中,会常遇到这样的需求:批量取消(删除)List中的数据.这就要求ListVIew支持批量选择.全选.单选等等功能,做一个比较强大的ListView批量选择功能是很有必要的,那如何做呢? ...

  3. Mysql 锁表处理

    -- 查看正在被锁定的的表 show ; -- 查看进程号 show processlist; -- 杀掉进程 : -- 表级锁次数 show status like 'Table%'; +----- ...

  4. day3_python之函数返回值、语句形式、表达式形式

    一. 函数对象 1. 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二.返回值 return的返回值没有类型 ...

  5. Java模板引擎FreeMarker介绍和使用

    http://blog.csdn.net/shimiso/article/details/8778793

  6. 2019年ICPC南昌网络赛 J. Distance on the tree 树链剖分+主席树

    边权转点权,每次遍历到下一个点,把走个这条边的权值加入主席树中即可. #include<iostream> #include<algorithm> #include<st ...

  7. HDU-6668-Game 百度之星第一场B

    在多个连续的区间段中,选出连续重复度最高的区间,这样连续选出多个重复度最高的不相交区间,然后从第一个区间的左边已经右边开始,连续贪心即可,答案取最小值 #include<iostream> ...

  8. ubuntu环境变量的三种设置方法

    一:设置环境变量的三种方法 1.1 临时设置 export PATH=/home/yan/share/usr/local/arm/3.4.1/bin:$PATH 1.2 当前用户的全局设置 打开~/. ...

  9. springmvc使用javabean作为请求参数

    1  首先写两个javabean对象  person 和 address 代码如下.两个类之间的关系如代码中 package cn.bean.demo.bo; public class Person ...

  10. mac上的mysql管理工具sequel pro

    https://blog.csdn.net/wan_zaiyunduan/article/details/54909389 以前用过Plsql.Navicat.Workbench,现在换到mac上,用 ...