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

给出一个长度为\(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. Python深入:02浅拷贝深拷贝

    对象赋值实际上是简单的对象引用.也就是说当你创建一个对象,然后把它赋给另一个变量的时候,Python并没有拷贝这个对象,而只是拷贝了这个对象的引用. 假设想创建一对小夫妻的通用档案,名为person. ...

  2. 消息点击率翻倍的背后——闲鱼无侵入可扩展IFTTT系统

    一.面临问题 在闲鱼生态里,用户之间会有很多种关系.其中大部分关系是由买家触发,联系到卖家,比如买家通过搜索.收藏.聊天等动作与卖家产生联系:另外一部分是平台与用户之间的关系.对这些关系分析之后我们发 ...

  3. ORA错误查询手册

    ORA-00910: 指定した長さがデータ型に対して長すぎます 原因: データ型CHARまたはRAWに対して指定した長さは.2000を超える値または4000を超える値であるため無効です. 処置: 指定 ...

  4. 重置Mysql自增列的开始序号

    ALTER TABLE  TableName AUTO_INCREMENT = 5; 代表重新从5开始(包括5)

  5. iptables禁止QQ端口

    #iptables -D FORWARD -p udp --dport 8000 -j REJECT

  6. 【原生JS】简单取随机数

    因为js本身取随机数仅能取 0 到 1之间的数,所以..... 取 1 - 10之间的随机数. function getSJS(x,y,z){ var int = null; while(int &g ...

  7. 【CSS3】3D立方体动画

    关于CSS3的3D立方体动画 知识点: 1.每个元素有独立的坐标系 2.坐标系随当前元素的改变而发生改变 3.立方体由静态transform参数构成 4.通过给容器添加动画使立方体运动 效果图: &l ...

  8. html5在微信中不允许放大缩小页面

    在头部添加 <meta name="viewport" content="width=device-width, initial-scale=1, maximum- ...

  9. Python--day25--面向对象之封装

    狭义上的封装的例子:(例1)Python就只有两种类型:公有和私有,没有Java中说的那种保护类型 例2: 例3:正常的方法调用私有方法 封装总结:

  10. 5-1rquests模拟登陆知乎之httpcode

    1,状态码: 400错误:请求无效 (Bad request);出现这个请求无效报错说明请求没有进入到后台服务里 2,requests库:python常用的库,有空仔细阅读一下官方文档