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

给出一个长度为\(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基础:常用函数

    1:enumerate enumerate(sequence, start=0) 该函数返回一个enumerate对象(一个迭代器).其中的sequence参数可以是序列.迭代器或者支持迭代的其他对象 ...

  2. selenium webdriver学习(一)------------快速开始(转载JARVI)

    selenium webdriver学习(一)------------快速开始 博客分类: Selenium-webdriver selenium webdriver 学习 selenium webd ...

  3. poj 3169 Layout (差分约束)

    3169 -- Layout 继续差分约束. 这题要判起点终点是否连通,并且要判负环,所以要用到spfa. 对于ML的边,要求两者之间距离要小于给定值,于是构建(a)->(b)=c的边.同理,对 ...

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

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

  5. C#面向对象--练习题

    public class Program { //用户自定义:我们自己定义这种数据类型 public struct jiegouti// 结构体前面加上public, 可供外界访问 { public ...

  6. 在Element节点上进行Xpath

    XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); try { ...

  7. 走过的laravel-admin 的坑

    一.http://laravel-admin.org/docs/#/zh/  大家可以根据这个安装1.5 版本的laravel后台管理, 他很方便哦,有很多方法他都自己自己封装了. 二.大家如果想好好 ...

  8. 使用Fiddler抓取微信饿了么小程序数据

    使用Fiddler抓取微信饿了么小程序数据 准备 一部装载Android 7.0以下的手机:此处使用华为荣耀5x 微信小程序7.0以下版本:此处为6.6.7.此处可通过豌豆荚应用下载. 安装好的Fid ...

  9. Spring Cloud探路(二) Erueka客户端的建立

    接上篇 1.pom.xml与上篇一致 2.新建包及Application启动类 @Configuration @ComponentScan @EnableEurekaClient @EnableAut ...

  10. 【js】 vue 2.5.1 源码学习(十二)模板编译

    大体思路(十) 本节内容: 1. baseoptions 参数分析 2. options 参数分析 3. parse 编译器 4. parseHTNL 函数解析 // parse 解析 parser- ...