P8868 比赛 题解

传送门

题意简述:给定序列 a,bm 次查询,每次询问 i=lirij=iri(maxk=ijak×maxk=ijbk)

n,m2.5×105

【8pts:暴力】

暴力枚举。

【20pts:关注贡献集合的变化,O(1)更新】

离线,右端点排序,假设此时循环到右端点为 r

定义:xi=maxj=ir{aj}yi 类似定义。fi=xi×yi

在右端点为 r 时,我们发现,fi 就是当 p=i,q=r 时的答案。

于是在右端点为 r 时,i=lrfi 就是当 p=l,l+1,,r,q=r 时的答案。

再进一步,在右端点为 l,l+1,,r 的时候,所有 i=lrfi 的和就是询问 (l,r) 的答案。

但是如果我们在当前求 fi 时,要找出所有包含 fi 的询问累加,实在是太麻烦,太慢了。

因此可以转而在 fi 上累加贡献:当右端点为 r 时,令 Fi 代表 p=i,q=ir 时的答案和。(注意,这里变的是右端点,左端点不变

因为左端点不变,所以 rr+1 时, Fi[i,ir] 的部分不会变,我们只需加入 [i,r+1] 的贡献,也就是 maxj=ir+1aj×maxj=ir+1bj=xi×yi,可以 O(1) 求得。

r 右移时,更新是:

x[i] = max(x[i], x[r]);
y[i] = max(y[i], y[r]);
F[i] += x[i] * y[i]; //注意这里的 +=

答案就是:

for (int i = l; i <= r; i++)
    ans[id] += F[i];

因为 x,y,F 都要随着 i 的右移更新多次,所以 x,y,F 的更新是 O(n2) 的;每次询问要循环,所以询问复杂度 O(n)。总复杂度 O(n2+nq)

【小 Question】

为什么上面 Fi 定义为 p=i,q=ir 的答案的和,而非 p=ir,q=r 的答案的和?

因为这样在 r 右移的过程中,Fi 的定义中 p,q 都与 r 有关系,我们就需要补充 [ir+1,r+1] 的贡献。前一种定义只需要补充 [i,r+1] 的贡献,因为 pr 无关。

【100pts】

一个很显眼的优化就是可以用线段树维护 x,y:先用单调栈预处理每个数左边大于它的数,然后区间赋值。

接下来尝试用线段树维护 Fi,这样对于询问 [l,r],我们能 O(logn) 的求出 Fi

我们只有三种操作:区间赋值 x,区间赋值 y,区间加上这个区间内所有单个位置的 x×y(简记为区间 +x×y)。

考虑我们要对一个节点保存什么信息:

  1. 答案 ans,也就是这个区间的 Fi 和。

  2. 区间内每个位置 X×Y 的和 sumxy,这样方便执行区间 +x×y

  3. 区间内 x 的和 sumx,区间内 y 的和 sumy。这样在区间赋值 x,y 的时候,能快速更新 sumxy

然后考虑一个标记保存什么信息:

  1. setx,sety 显然需要,即区间赋值 x,y 的标记。

  2. addxy,即这个区间应当加上 addxyx×y

虽然上面两个标记已经包含了题目的信息,但是当两个标记碰在一起的时候,我们发现无法规定一个合适的优先级,使得两个标记能等价地合并成一个标记。

比如:当我们有一个 +1x×y 的标记,但是遇到了 setx 标记。此时的 addxy 如果还是 1,加出来的 x×y 就是 set 之后的 x×y 了,肯定不对;但是这一个 +1x×y 的增加也不能丢掉,不然答案又少了。

这样就卡住了。

那怎么办呢?当你发现线段树的标记无法完美合并的时候,可以考虑一下多记录一些信息。

考虑我们上面遇到的问题:当 addxy 遇到 set 的时候,没有地方可以存原先的 addxy 了!

因此我们考虑设置一个暂存点:addC,表示这个区间每一个位置的答案都要加上 addC 这么多数值,总答案也就是加上 addC×len 的数值。

错误示范:如果 addxy 遇到 set,我们可以调用这个标签所在的节点的 x×y 值,加入 addC 中,同时 addxy0

这是不对的!

线段树的 Tag*Tag,一定不能涉及到 Val 的属性!!!

因为当我们 pushdown 的时候,Val 的属性就会变化,Tag 也要跟着变,太麻烦了!

发现增加了一个 addC 还是不能合并,原因在于:如果 pushdown 了,addC 不能同时加到两个子区间上,因为这个 addC 是大区间的 addC,小区间需要根据小区间自己的 sumx,sumy 增加。

既然小区间需要根据小区间的 sumx,sumy 打标记,我们自然想到在 Tag 上再额外记录两个信息:addx,addy,表示这个区间需要 +addx×sumx,+addy×sumy

这样就对了,当 addxy 遇到 set 的时候,可以通过 set 的值,把增加的倍数加入 sumx,sumy 里面。

同时,我们规定:一个 Tag,加法操作的优先级高于赋值操作。比如 Tag:adx=3,covx=2,加的是原来的sumx,不是covx*len

【总结】

Val 包含的信息:

  1. ans,答案;

  2. sumX,区间内 X 的和;

  3. sumY,区间内 Y 的和;

  4. sumXY,区间内 X*Y 的和。

Tag 包含的信息(加法的优先级高于赋值):

  1. setx, sety:X,Y 的赋值信息;

  2. addXY:这个区间加了多少次 X*Y;

  3. addX:这个区间加了多少次 sumX;

  4. addY:这个区间加了多少次 sumY;

  5. addC:这个区间的答案要加上 addC*len。

所以一个区间的答案就是 ans+addXY×sumXY+addX×sumX+addY×sumY+addC×len.

Tag * Tag 怎么写?用标记队列的思想。

posted @   FLY_lai  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示