[省选联考2020]冰火战士

题意简述

https://loj.ac/p/3299

战士有冰火两种类型,有体温和能量两种属性。每个时刻,体温 \(\le\) 环境温度的冰战士集合会和体温 \(\ge\) 环境温度的火战士集合搏斗,直到其中一个集合的总能量耗尽。找到一个环境温度,使得冰火双方所耗能量之和最大,在有多个环境温度满足的情况下,取环境温度的最大值。输出这个环境温度以及此时冰火双方所耗能量之和。

题解

这是一道卡常题,在个人代码常数较大的情况下需要采用 fread/fwrite,但还是有一些人不卡常常数还是很小,让人不能理解。


根据题意,每次对阵的答案应该是冰战队和火战队的能量较小值的 2 倍。乘 2 不会影响单调性,记 \(I(t),F(t)\) 分别表示冰火在环境温度 \(t\) 下的可用战士能量和。我们画出 \(I(t),F(t),\min(I(t),F(t))\) 的图像。

image

因此这是一个单峰函数(非严格)。

考虑如何找到这个峰。
引理 最适温度必然是一个战士的体温。证明不赘述。
假如我们找到满足 \(I(t_1)\le F(t_1)\) 的最大的 \(t_1\),那么必有 \(I(t_1+1)>F(t_1+1)\),再找到 \(F(t_2)=F(t_1+1)\) 的最大的 \(t_2\),比较 \(F(t_2)\)\(I(t_1)\) 的大小权衡出答案。

于是有下面两种思路。

思路一

二分这个 \(t_1\),再二分这个 \(t_2\)(以 \(F(t_2)\ge F(t_1+1)\) 为比较依据),每次 check 时查询 \(I(t),F(t)\) 中的一个或两个进行比较。考虑如何得到一个温度下的 \(I(t)\)。本质上是一个值域上的前缀和,只需要对战士体温离散化再使用树状数组维护即可。一轮二分复杂度 \(O(\log^2n)\),难以通过。

思路二 树状数组上二分

这是一道树状数组上二分的模板题,讲一讲树状数组上二分的过程。
BIT 上二分是一个倍增的过程。首先需要熟谙树状数组的一条性质:

  • 对于当前位置 \(p\),任意 \(i\in \mathbf{N},p+2^i\le n\),一定有 \(c_{p+2^i}\) 记录了 \([p+1,p+2^i]\) 这一段的信息。

那么就可以利用倍增的思想,从大到小枚举 \(i\),若当前的前缀和 \(+c_{p+2^i}\) 是【满足条件】的,则 \(p\gets p+2^i\)。最后一定可以跳到一个最后一个【满足条件】的位置,从这一方面,有跟二分有类似之处。

分析时间复杂度。由于 check 是否【满足条件】是 \(O(1)\) 的,因此只是倍增的复杂度,即 \(O(\log n)\),降解了一个 \(\log\)
考虑如何实现。我们在冰战士的树状数组上跳,并结合火的后缀和检查。如何 \(O(1)\) 地查询后缀和呢?由于后缀和=总和-前缀和,我们可以同步地在火的树状数组上跳,那么我们就可以查询到 \(>p\)(当前位置) 的火战士能量和,但我们想要的是 \(\ge\) 的,因此改为定义火的树状数组查询到的前缀和为体温小于下标的火战士能量之和,即:

void iceInsert(int x,int y){
	for(int i=x;i<=l;i+=i&-i)c[0][i]+=y;
}
void fireInsert(int x,int y){
	for(int i=x+1;i<=l;i+=i&-i)c[1][i]+=y;
	sfire+=y;
}

那么就会出现一个可能比较常犯的错误,即能否偏要反着来,只把原来定义的火树状数组向右平移一个单位呢?答案是否定的。这样一来我们查询到的其实是一个同构树状数组的前一个下标所代表的区间和,而这是不一定满足原来的性质的。解释含糊,意会一下。总之我们定义我们的前缀和为 \(<\) 就一定可以在倍增的时候同步跳。

提交记录

卡常前(注意,不要开O2,当年没有)
卡常后
建议结合卡常前理解思路。

另外,要赶快学会fread/fwrite

posted @ 2021-12-08 21:20  pengyule  阅读(62)  评论(0编辑  收藏  举报