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

给出一个长度为\(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. oracle函数 RTRIM(c1,[,c2])

    [功能]删除右边出现的字符串 [参数]C1 字符串 c2 追加字符串,默认为空格 [返回]字符型 [示例] SQL> select RTRIM('gao qian jingXXXX','X') ...

  2. php 正则表达式怎么匹配标签里面的style?

    $str = '<div style="margin:0px;text-align:left;padding:0px;">任意内容</div>'; $reg ...

  3. SVN更换新的svn链接

    输入新的SVN地址即可:

  4. PHPstorm相关设置以及快捷键

    转自:http://blog.csdn.net/fenglailea/article/details/12166617 1.界面中文方框问题 Settings->Appearance中Theme ...

  5. HTML的优化

    HTML的优化 : 1).h标签的使用: 要注意的是,不论任何页面,h1标签只能出现一次,它是当前页面的主标题,权重最高, 所以要慎用 . 一般情况下,如果有关键词的话最好是在h1里面出现. h2是表 ...

  6. javascript 元素的大小

    1.偏移量 元素的可见大小由其高度.宽度决定,包括所有内边距.滚动条和边框大小(不包含外边距).通过下列4个属性可以获取元素的偏移量: offsetHeight: offsetWidth: offse ...

  7. setTimeout与setInterval有何区别?

    ①setTimeout和setInterval的语法相同.它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码. ②不过这两个函数还是有 ...

  8. C#循环语句练习(三)

    for循环拥有两类:一.穷举:把所有可能的情况都走一遍,使用if条件筛选出来满足条件的情况. (1).羽毛球拍15元,球3元,水2元.200元每种至少一个,有多少可能. (2).百鸡百钱:公鸡2文钱一 ...

  9. RBF神经网络通用函数 newrb, newrbe

      RBF神经网络通用函数 newrb, newrbe 1.newrb 其中P为输入向量,T为输出向量,GOAL为均方误差的目标,SPREED为径向基的扩展速度.返回值是一个构建好的网络,用newrb ...

  10. Codeforces Round #179 (Div. 1 + Div. 2)

    A. Yaroslav and Permutations 值相同的个数不能超过\(\lfloor \frac{n + 1}{2} \rfloor\). B. Yaroslav and Two Stri ...