[CF1696] Codeforces Global Round 21 题解
CF link:
\(\texttt{A. NIT orz!}\)
\(\texttt{B. NIT Destroys the Universe}\)
\(\texttt{C. Fishingprince Plays With Array}\)
\(\texttt{D. Permutation Graph}\)
\(\texttt{E. Placing Jinas}\)
\(\texttt{F. Tree Recovery}\)
\(\texttt{G. Fishingprince Plays With Array Again}\)
\(\texttt{H. Maximum Product?}\)
A. NIT orz!
Statement
给定一个集合 \(a\) 与一个数 \(z\)。你可以进行操作:选择 \(a\) 中的一个元素 \(x\) 并使得 \(x\gets x\operatorname{bitor} z,z=\gets x\operatorname{bitand} z\)。
求进行任意次操作后 \(a\) 中的最大值。
Solution
容易发现最优情况的最大值只可能是第一次操作的结果,直接输出 \(a\) 中所有元素与 \(z\) 的或的最大值,代码实现无难度。
B. NIT Destroys the Universe
Statement
定义 \(\operatorname{mex}(a)\) 表示集合 \(a\) 中没出现的最小自然数。
给定序列 \(a\),你可以进行操作:选择一组 \(l,r\) 并使得 \(a\) 中位置在 \([l,r]\) 区间内的元素都改为这些元素的 \(\operatorname{mex}\),即 \(\forall i\in[l,r]\cap\mathbb Z,\ a_i\gets\operatorname{mex}(\{a_i|i\in[l,r]\cap\mathbb Z\})\)。
求最少进行几次操作可以使整个序列为 \(0\)。
Solution
发现最劣情况下最多对整个序列操作两次,如果第一次操作若初始序列中没有 \(0\) 则结果为 \(0\),直接结束,否则 \(\operatorname{mex}\) 的结果非 \(0\),整个序列无 \(0\),则第二次结果为 \(0\)。
但这并不是最优的,发现如果序列两端有 \(0\) 可以通过缩小左右端点使答案可能变小,所以就是找到非 \(0\) 的最靠前和最靠后的数,找两者之间有没有 \(0\),直接判断输出。
记得特判整个序列为 \(0\) 的情况,代码实现依然无难度。
C. Fishingprince Plays With Array
Statement
给定两个序列 \(a\),\(b\) 以及一个参数 \(m\),你可以进行以下两种操作:
- 选定一个 \(a\) 序列中的元素 \(x\) 将其在原位置分裂为 \(m\) 个 \(\frac xm\);
- 选定 \(a\) 序列中连续的 \(m\) 个 \(x\) 将其在原位置合并为一个 \(mx\)。
求进行任意次操作能否使 \(a\) 与 \(b\) 相同。
Solution
发现两种操作是互逆的,但是 \(1\) 操作比 \(2\) 操作更具广泛性,因为 \(2\) 操作中假如出现连续超过 \(m\) 个数你不知道该合并哪一些,所以我们考虑将 \(a\) 和 \(b\) 都做 \(1\) 操作直到不能做,即序列中没有 \(m\) 的倍数,最后比较就好了。
在代码实现上有一个注意点就是因为数量很大所以可以用一个 vector
存一个数对 \((x,c)\) 表示 \(c\) 个 \(x\),最后将维护的两个 vector
都化为最简后直接比较就好了,代码实现不难。
D. Permutation Graph
Statement
给定一个序列 \(a\),\(a\) 中是一个排列,每次你可以从位置 \(i\) 跳到位置 \(j\) 当且仅当 \(a_i=\max_{k=i}^ja_k\land a_j=\min_{k=i}^ja_k\lor a_i=\min_{k=i}^ja_k\land a_j=\max_{k=i}^ja_k\),即两端分别为区间最大值和最小值,求从下标 \(1\) 跳到下标 \(n\) 的最小移动次数。
Solution
首先想一个比较粗略且正确性不明的贪心,每次找能跳到的最靠右的点,毛想想感觉挺对的,所以尝试着证明一下(说不定成功了呢)。
考虑如果能从 \(i\) 到达 \(j\) 和 \(k\),且 \(k\) 是能到达的最远的点,假设 \(a_i=\min_{p=i}^ka_p\)(另一种情况证明相同),则 \(i<j<k\land a_i<a_j<a_k\),此时我们发现下一步 \(j\) 能跳到的 \(k\) 一定都能跳到,而 \(k\) 能比 \(j\) 跳得更远(\(j\) 会被 \(k\) 卡住),此时 \(k\) 优于 \(j\)。另一种情况是先往回走,这种情况显然不会更优,因为往左走再往右走如果是先变小再变大(另一种情况同理,两次变小不可能)不如直接往右跳,离原来的终点不可能走超过两步(你需要往回走说明右边有比你更小的,直接往能跳到的最小的上跳)。
接下来是算法实现,我们首先需要知道往右边跳最远跳到哪里,此时如果你是 \(\min\),则你与终点之间不能有比你更小的,大的同理,所以先维护两个数组 \(\mathrm{less}_i/\mathrm{greater}_i\) 表示在 \(i\) 右边比 \(a_i\) 小/大的最近的点,这样我们就知道我们跳的边界。
然后我们会发现如果 \(\mathrm{less}_i>\mathrm{greater}_i\),我们肯定是作为最小值往大的跳,因为如果作为大的往小的跳最远不超过 \(\mathrm{greater}_i\),而往大的跳至少也能到 \(\mathrm{greater}_i\),反之亦然。
然后知道了边界与选择,就是跳到哪里的问题,因为要跳到最远,所以肯定是跳到可选区间最值处,即若 \(i\) 作为最小值就跳到 \(\max_{j=i}^{less_i-1}a_j\) 所对应的下标处,因为再远就跳不到了,而跳得更近会亏,所以显然需要维护一个 ST 表。
最后总结就是先预处理两个数组,再维护一个同时维护 \(\min/\max\) 的 ST 表,代码实现没有想象中那么难。
E. Placing Jinas
Statement
给定一张无限大的棋盘初始时在 \((0,0)\) 处有一个棋子,你可以进行操作:将 \((i,j)\) 处的棋子拿走,在 \((i,j+1),(i+1,j)\) 处各放置一枚棋子。
现给定一个从 \(0\) 开始的无限长的单调不增的序列 \(a\) 的前 \(n\) 个元素,后面的所有元素为 \(0\),求使得第 \(i\) 行的所有棋子都不在 \([0,a_i)\) 范围内的最少操作次数。
Solution
首先发现操作次数固定,不管使用什么策略每个位置上的总操作数总是固定的,问题就转化成求每个位置的操作数。
然后再进行一步转化,既然题面中是按行单调递减,我们也考虑按行考虑,当我们开始处理第 \(i\) 行时每个位置的棋子数。
可以通过手模几组数据显然地得知每行每个位置的初始棋子数都是上一行的前缀和,然后是一个经典的套路,就是高阶前缀和可以用组合数解决,所以预处理组合数,再直接算就好了。
具体而言,第 \(i\) 行的第 \(j\) 个位置的棋子数(\(i,j\) 都从 \(0\) 开始)是只有 \(a_0=1\) 的序列的 \(i\) 阶前缀和的第 \(j\) 位,结果是 \(\dbinom{i+j-1}j\)。
然后计算每个位置的操作数,可以通过前面的棋子数推出,第 \(i\) 行的第 \(j\) 个位置的操作数是第 \(i\) 前 \(j\) 个位置的棋子数,所以要再做一遍前缀和,答案就是 \(\dbinom{i+j}j\)。
最后一个一个位置算肯定是不行的,所以要一行一行算,所以每行对前面的式子做前缀和,注意因为上面的 \([0,a_i)\) 是开区间,所以答案就是:
代码实现比上一题还简单得多。
F. Tree Recovery
Statement
给定一个大小为 \(n^3\) 的三维 \(0/1\) 数组 \(a_{i,j,k}\),判断是否存在至少一棵包含 \(n\) 个节点的树满足 \(a_{i,j,k}=1\) 当且仅当 \(\operatorname{dist}(i,k)=\operatorname{dist}(i,k)\),若存在则再将任意一棵满足要求的树输出。
Solution
考场一直在想神仙构造方法,实际上连最浅显的结论都没有发现。
首先关注到数据规模不大,想到通过某种方法构造完一棵树后暴力 \(\mathcal O\left(n^3\right)\) 判断是否满足要求的可行性。
这题还是比较需要思想火花的,如果想不到第一步的话根本做不下去,我们可以首先发现一个结论,就是如果确定一条边存在的话,假设这条边为 \(\langle x,y\rangle\),因为这条边的存在,我们已经得知 \(\operatorname{dist}(x,y)=1\),则如果存在 \(a_{x,z,y}=1\),则说明 \(\operatorname{dist}(y,z)=\operatorname{dist}(x,y)=1\),说明边 \(\langle y,z\rangle\) 也是存在的,以此类推我们可以得知图中所有的边。
其次,我们发现满足要求的树应当是唯一的,如果存在多种构造方案,即有一条边在某种方案中存在,在另一种方案中不存在,将这条边放在它不存在的方案中将形成一个环,这个环上任意相邻的两条边 \(\langle x,y\rangle,\langle y,z\rangle\) 都满足 \(a_{x,z,y}=1\),则任意删去一条边都会导致存在一个不满足的条件。
那么我们现在就是要找到一条在最终答案中存在的边,因为数据规模不大,所以我们可以直接暴力枚举一条以 \(1\) 为端点的边,因为总有一条端点为 \(1\) 的边是被包含到答案里的,所以直接枚举构造,最后判断答案是否满足要求,时间复杂度 \(\mathcal O\left(n^4\right)\),实际上因为跑不满所以很快,代码实现不难。
G. Fishingprince Plays With Array Again
Statement
定义关于两个参数 \(X\),\(Y\) 的对于序列 \(a\) 的函数 \(f(a)\):
你可以对序列从 \(1\) 开始长度为 \(n\) 的 \(a\) 进行以下两种操作:
- 在位置 \(i\ (1\le i<n)\) 花费 \(t\) 秒,使 \(a_i\gets a_i-Xt,a_{i+1}\gets a_i-Yt\)。
- 在位置 \(i\ (1\le i<n)\) 花费 \(t\) 秒,使 \(a_i\gets a_i-Yt,a_{i+1}\gets a_i-Xt\)。
\(f(a)\) 为通过上述两个操作使得 \(a\) 序列中所有元素小于等于 \(0\) 所需要花费的最小时间。
给定参数 \(X\),\(Y\) 与序列 \(a\),每次进行下列两个操作中的一个:
1 k v
将 \(a_k\) 变为 \(v\)。2 l r
将 \(a\) 序列中 \([l,r]\) 区间范围内的元素以原顺序取出作为序列 \(b\),求 \(f(b)\)。
Solution
考虑如果没有修改,单次查询怎么做。
我们可以先将询问的式子写出来,我们设在 \(i\) 位置花费在 \(1\) 操作上的时间为 \(t_{i,1}\),同理花费在 \(2\) 操作上的时间为 \(t_{i,2}\),则可以得到下面的式子:
可以看出这是一个线性规划,但现在这个线性规划的未知数实在太多,反而限制比未知数还少,我们考虑得到该问题的对偶问题。
首先这个式子非常难看,所以我们很难一下子找到它的对偶问题,所以把整个方程先展开成表格(这个表格可能会有一点大,空格处都为 \(0\)):
\(t_{1,1}\) | \(t_{1,2}\) | \(t_{2,1}\) | \(t_{2,2}\) | \(t_{3,1}\) | \(t_{3,2}\) | \(\cdots\) | \(t_{n-2,1}\) | \(t_{n-2,2}\) | \(t_{n-1,1}\) | \(t_{n-1,2}\) | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
\(X\) | \(Y\) | \(\cdots\) | \(\ge\) | \(a_1\) | ||||||||
\(Y\) | \(X\) | \(X\) | \(Y\) | \(\cdots\) | \(\ge\) | \(a_2\) | ||||||
\(Y\) | \(X\) | \(X\) | \(Y\) | \(\cdots\) | \(\ge\) | \(a_3\) | ||||||
\(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\ddots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) | \(\vdots\) |
\(\cdots\) | \(X\) | \(Y\) | \(\ge\) | \(a_{n-2}\) | ||||||||
\(\cdots\) | \(Y\) | \(X\) | \(X\) | \(Y\) | \(\ge\) | \(a_{n-1}\) | ||||||
\(\cdots\) | \(Y\) | \(X\) | \(\ge\) | \(a_n\) |
这下好找多了,把方程重新竖着写,答案就直接出来了,对偶问题就是:
这个式子看上去比起上面的那是相当好看了,然后我们思考如何求解这个线性规划。
解决这个问题的最大阻碍是每个变量的可取值太多,但是介于这些约束条件是特别优美的,我们可以试图挖掘一些性质,首先给出结论:令 \(X>Y\)(所有关于 \(X\),\(Y\) 的式子都是对称的,所以无所谓),则 \(\forall i\in[1,n]\cap\mathbb Z,\ x_i\in\{\frac1X,\frac1{X+Y},0\}\),下面给出两种证明:
从一个比较粗略感性的角度来想,我们先给所有变量随机分配一个值,然后对其进行调整,由于所有约束条件都只约束相邻两个,所以我们肯定是在相邻的变量上做文章,可以想象我们的调整应该会是将一个值升高或降低再将其两边进行对应调整,同样也可以猜测结果的边界,因为会一直调整,所以结果要么是一个满的两边都是空的,要么是相邻两个相等,前者情况是中间 \(\frac1X\),两边 \(0\),后者是相邻两个都为 \(\frac1{X+Y}\)。
更加理性的证明并不是没有,我们知道一个线性规划问题的解本质上是在求一个高维凸包的切,其答案必然经过高维凸包的至少一个端点,而穿过端点说明它会使一些约束条件到达边界,且显然每一个变量都至少被一个约束条件约束到边界上,我们接下来进行一些构造,我们将每个变量视为一个点,我们将所有到达边界的约束条件取出用于连边,如果一个约束条件只和一个点有关(约束中的另一个点为 \(0\) 或本身为 \(0\)),则连接一个自环,否则如果该约束条件与 \(i\) 和 \(i+1\) 有关,则连接一条边 \(\langle i,i+1\rangle\),结论是这张图中任意一个点和任意一条边都会被包含在环里,可以进行证明:
点的部分可以在边的部分证明完成后自然得证,所以我们率先证明边的结论:首先发现如果不是环就只可能是一条链,链中的节点一定是连续的,我们将所有节点分成奇偶两类,可以很容易地得知同一类中所有点对应的变量的取值都是相同的,假设变量取值更大的集合是 \(A\),另一个集合是 \(B\),因为如果 \(A\) 与 \(B\) 中元素不可能相等,因为相等时即同时满足 \(i\) 与 \(i+1\) 间的两个约束条件,形成一个二元环,否则肯定满足 \(\forall i\in A,j\in B,\ Xx_i+Yx_j=1\),证明显然,我们可以发现此时我们令 \(A\) 集合对应位置的序列 \(a\) 中元素的和为 \(s_A\),同理 \(B\) 集合对应的和为 \(s_B\),我们此时发现,如果任何 \(A\) 集合中元素变化 \(Yd\),则 \(B\) 集合中元素反向 \(Xd\),此时所有约束保持不变,若 \(Xs_B>Ys_A\),则此时可以将集合 \(B\) 中的元素一直增大到临界点,即 \(A\) 中元素与 \(B\) 中元素相等,此时若继续变化还可以更优就是另一种情况,即 \(Ys_A>Xs_B\),此时可以一直增加直到 \(B\) 中所有元素的值为 \(0\),则整条链消失,每个元素被自环约束,且因为刚刚的操作一直在使答案增加,所以刚刚是链的情况显然不会是最优解,若满足 \(Ys_A=Xs_B\),我们发现已经满足 \(s_A>s_B\),当 \(B\) 最开始增加时答案不变,而当其达到临界点后 \(A\) 和 \(B\) 发生反转,此时一直增加答案达到最优。
上面证明了每条边在环内,而每个变量至少被一个约束条件边界约束,自然得证。
我们发现自环的情况就是 \(0\) 和 \(\frac1X\),而二元环中两元素相等同时满足两个约束即 \(\frac1{X+Y}\),我们就证明了答案只可能从三种情况产生(上面的证明可能有误,但结论正确,如有纰漏欢迎指正)。
现在我们就可以非常简单地解决单次查询了,可以使用 dp,发现动态询问是经典的单点修改区间查询,所以考虑使用线段树,而我们的 dp 正好满足区间可加性,设 \(f_{l,r,0/1/2,0/1/2}\) 表示 \([l,r]\) 区间内左端点取 \(0/\frac1{X+Y}/\frac1X\),右端点同理的最优解,可以简单地动态维护,这里代码实现上可以使用一点小技巧,就是这个方程的转移形如可以稍微改一下 dp 状态定义,设 \(f_{l,r,0/1/2,0/1/2}\) 表示 \([l,r]\) 区间内左端点取 \(0/\frac1{X+Y}/\frac1X\),右端点后一位最大允许取 \(0/\frac1{X+Y}/\frac1X\),此时转移形如 \(f_{l,r,i,j}=\max_{k\in\{0,1,2\}}f_{l,mid,i,k}+f_{mid+1,r,k,j}\),转移形似矩阵乘法,实际上就是广义矩乘,可以直接用矩阵类实现,状态中的 \(l,r\) 其实在线段树内部自带,无需维护。
代码相较于思路上的头脑风暴,可以说是非常简单了。
H. Maximum Product?
Statement
定义关于参数 \(m\) 对于可重集 \(S\) 的函数 \(f(S)\) 表示在其中选 \(m\) 的乘积的最大值,即 \(\max_{T\subset S,|T|=m}\prod_{x\in T}x\)。
给定参数 \(m\) 和可重集 \(S\),求对于 \(S\) 的所有大小至少为 \(m\) 子集 \(T\) 的 \(f(T)\) 的和,即求 \(\sum_{T\subset S,|T|\ge m}f(T)\)。
注意本题中可重集中值相同的每个元素各不相同,你可以认为本题中将所有元素放入数组中后以下标作为区别标准,即大小为 \(n\) 的集合包括空集必然存在 \(2^n\) 个集合。
Solution
比起上一题的各种弯弯绕绕,这一题的解法简单明了,也暴力得多。
直接暴力求题目要求的东西实在太暴力,几乎无法优化,所以考虑转成计数题。
首先思考给定可重集 \(S\) 求 \(f(S)\),如果集合中只有正数可以直接取前 \(m\) 大值,否则进行分类讨论,将绝对值前 \(m\) 大取出,如果负数数量为偶数则没有问题,否则考虑将一个正数换成负数或将一个负数换成正数,这取决于不在这 \(m\) 个数中的绝对值最大的正数和负数的大小,一种特殊情况是集合中只有负数且要选计数个数,此时选择绝对值最小的 \(m\) 个负数。
通过上面的计算方式,我们得出几个结论,首先一般情况下我们会优先取绝对值更大的数,其次有正负分类讨论,所以我们先对数据进行第一波处理——将读入的集合分成两部分 \(\text{pos}\) 与 \(\text{neg}\),两部分分别按绝对值从大到小排序。
接下来我们可以开始考虑对答案进行计算,这种最大值的和我们有一种比较固定的套路就是通过排序去除计算最大值的过程,然后转换成计算题,这次也不例外,我们可以用 dp 解决前 \(m\) 个数的乘积,即 \(f\) 的答案,然后再使用 \(2\) 的幂次快速计算后面集合数量部分。
计数题最大的难题在于不重不漏,即找到一个好的枚举指标,这里我们可以枚举绝对值前 \(m\) 大中最小的正数和负数,然后前半部分可以 dp,后半部分直接算。
首先考虑前半部分的 dp,\(fp_{i,j}\) 表示在 \(\mathrm{pos}_{1\sim i}\) 中取恰好 \(j\) 个数且必须包含 \(\mathrm{pos}_i\)的乘积的和,同理定义 \(fn_{i,j}\) 表示在 \(\mathrm{neg}_{1\sim i}\) 中取恰好 \(j\) 个数且必须包含 \(\mathrm{neg}_i\) 的乘积的和,这两个 dp 难度不大,暴力转移可以用前缀和优化一下,我们可以利用这两个数组来计算前 \(m\) 个数的乘积,然后通过预处理 \(2\) 的幂次可以计算方案数,记得要预处理每个数在所有数中绝对值从大到小的排名,这决定了用 \(2\) 的幂次计算方案数的结果。
但是正如上面讲解计算 \(f(S)\) 方法时所说的,当前 \(m\) 大时如果负数的数量为奇数,还要进行分类讨论,我们下一步枚举不被包含的最大的正数和负数,直接进行分类讨论计算,此时我们发现因为要枚举绝对值前 \(m\) 大中负数的个数、前 \(m\) 大中绝对值最小的正数、负数、不被包含的最大的正数、负数,一共要枚举五个,所以时间复杂度 \(\mathcal O\left(n^5\right)\),记得要把前 \(m\) 大只有正数和只有负数的部分取出来单独做,如果 \(m\) 为奇数,则要同上处理一个反着的数组用于计算选取集合中只有负数的情况和前 \(m\) 大只有负数但后面有正数的分类讨论,具体细节可以看这份 \(\mathcal O\left(n^5\right)\) 的暴力代码(建议先将这份代码彻底读懂再往下看,下面所有代码都基于这份代码优化)。
接下来其实就是轻车熟路了,首先发现时间复杂度瓶颈是在绝对值前 \(m\) 大数中有奇数个负数的情况,我们发现此时后半部分的计算与代码中枚举的 \(k\) 无关,可以将这部分提到前面,直接计算即可,预处理 \(h_{i,j}\) 表示当绝对值前 \(m\) 大中最小的正数为 \(\mathrm{pos}_i\),负数为 \(\mathrm{neg}_j\) 的贡献,这样时间复杂度就是 \(\mathcal O\left(n^4\right)\),这里是优化到 \(\mathcal O\left(n^4\right)\) 的代码。
最后一步,我们发现时间复杂度瓶颈又到了 \(h\) 的预处理上,同时是同时枚举正数和负数的部分,然后这部分卡住我们的是那个 \(\max\),还是那个套路,运用一些手段将 \(\max\) 去掉,考虑到我们之前已经排完序了,这里我们可以先将两个情况写出来,然后发现可以前缀和优化,最后就是用指针维护一下 \(\max\) 的临界点,然后用前缀和优化统计,降到 \(\mathcal O\left(n^3\right)\),达到可过的时间复杂度。
一步一步走到最后的代码,思路其实比上一题清晰得多,实现上对细节要求蛮多,整体是一道非常不错的题。