Codeforces Round 917 (Div. 2)

Least Product

题目

输入长度为 \(n\) 的数组 \(a\),输出能够使 \(\prod_{i=1}^{n}{a_{i}}\) 最小的最小操作次数。每次操作可以选择数组中的任意元素 \(a_{i}\),如果 \(a_{i}<0\),则可以将其改为 \([a_{i},0]\) 中的任意整数,否则可以将其改为 \([0,a_{i}]\) 中的任意整数。

数据范围:\(1\leq n\leq 100\),\(-10^{9}\leq a_{i}\leq 10^{9}\)。

思路

由于操作只会让所有元素乘积的绝对值变小,所以如果乘积是非正数,则不需要进行操作,否则只需进行一次操作,将任意一个元素改为 \(0\)。

Erase First or Second Letter

题目

输入长度为 \(n\) 的字符串 \(s\),输出进行任意次操作能够得到的不同非空字符串的个数。每次操作可以删除字符串的第一个字符或者第二个字符。

数据范围:\(1\leq n\leq 10^{5}\)。

思路

  • 方法一:枚举剩余字符串的第一个字符下标 \(i\),对于每个 \(i\) 都可以通过不断删除第二个字符得到 \(n-i\) 个不同的字符串。然后思考什么时候会出现相同的字符串,假设两次枚举的第一个字符分别为 \(i\) 和 \(j\)(\(i<j\)),只有当 \(s_{i}=s_{j}\) 时才有可能出现相同字符串,进一步观察可以发现,对 \(i\) 操作得到的 \(n-i\) 个不同的字符串总是包含对 \(j\) 操作得到的 \(n-j\) 个不同的字符串。所以,假设字符串 \(s\) 中有 \(m\) 个不同的字符,每个字符第一次出现的位置分别为 \(k_{0},k_{1},\dots,k_{m-1}\),则答案为 \(\sum_{i=0}^{m-1}(n-k_{i})\)。
  • 方法二:枚举剩余字符串的第二个字符下标 \(i\),对于每个 \(i\) 它的贡献为 \([0,i-1]\) 中不同字符的个数。

Watering an Array

题目

输入长度为 \(n\) 的整数数组 \(a\),长度为 \(k\) 的整数数组 \(v\),以及整数 \(d\)。输出执行 \(d\) 次操作能够得到的最大分数。有两种类型的操作:

  • 假设当前执行第 \(i\) 次操作,则将数组 \(a\) 的前缀 \([a_{1},a_{b_{i}}]\) 都加 \(1\)。其中 \(b_{i}=v_{((i-1)\bmod k)+1}\)。
  • 将 \(a_{j}=j\) 的元素个数加到分数中,然后将数组中的所有元素都置为 \(0\)。(下标从 \(1\) 开始)

数据范围:\(1\leq n\leq 2000\),\(1\leq k\leq 10^{5}\),\(k\leq d\leq 10^{9}\),\(0\leq a_{i}\leq n\),\(1\leq v_{i}\leq n\)。

思路

如果数组 \(a\) 中的元素都为 \(0\),显然最大分数为 \(\lfloor\frac{d}{2}\rfloor\),对应的方案为交替执行两种操作。也就是说,解决问题的关键是确定何时第一次将数组 \(a\) 重置。这可以通过枚举实现,但是由于 \(d\) 很大,肯定不能直接枚举范围 \([1,d]\)。进一步观察可以发现,对前缀进行 \(2n\) 次加法,再重置最多得到 \(n\) 分,而先重置再交替执行操作,能够得到的分数大于等于 \(n\),所以只需要枚举范围 \([1,2n]\)。

Yet Another Inversions Problem

题目

输入长度为 \(n\) 的数组 \(p\),表示 \([1,2n-1]\) 中所有奇数的一个排列。输入长度为 \(k\) 的数组 \(q\),表示 \([0,k-1]\) 中所有整数的一个排列。定义长度为 \(nk\) 的数组 \(a\),对于 \(0\leq i<n\) 和 \(0\leq j<k\),有 \(a_{i\cdot k+j}=p_{i}\cdot 2^{q_{j}}\)。输出数组 \(a\) 的逆序数,结果对 \(998244353\) 取余。

数据范围:\(1\leq n,k\leq 2\cdot 10^{5}\),\(1\leq p_{i}\leq 2n-1\),\(0\leq q_{i}<k\)。

思路

可以将数组看作 \(n\times k\) 的矩阵,逆序数可以分为行内和行间。所有行的行内逆序数都是数组 \(q\) 的逆序数,使用归并排序或者树状数组即可求解,所以难点在如何快速求出行间的逆序数。首先尝试两两枚举所有行,然后观察对于确定的两个行,它们的逆序数有什么特点。由此可以发现,两行之间的逆序数大概是一个等差数列之和,项数和其他边界条件是由 \(p_{i}\) 和 \(p_{j}\) 的大小关系确定的。具体来说,假设 \(p_{i}<p_{j}\),等差数列是由满足 \(p_{i}\cdot 2^{z}<p_{j}\) 条件的最大的 \(z\) 确定的,确定 \(z\) 需要花费 \(O(\log{n})\) 的时间。从而可以得到时间复杂度为 \(O(n^{2}\log{n}+k\log{k})\) 的朴素解法。(总是假设 \(i<j\))

下面解释等差数列是如何得到的,假设 \(p_{i}\cdot 2^{z}<p_{j}\)(\(z\geq0\)),将对应的两行合并之后可以得到如下序列:

$$ p_{i}\cdot 2^{0},p_{i}\cdot 2^{1},\dots,p_{i}\cdot 2^{z},p_{j}\cdot 2^{0},p_{i}\cdot 2^{z+1},p_{j}\cdot 2^{1},\dots,p_{i}\cdot 2^{k-1},p_{j}\cdot 2^{k-z-1},\dots,p_{j}\cdot 2^{k-1} $$

要求逆序数,可以通过对每个 \(p_{i}\) 项前面有多少个 \(p_{j}\) 项计数,然后求和得到。可以发现,\(p_{i}\cdot 2^{z+1}\) 前面有 \(1\) 个 \(p_{j}\) 项,\(p_{i}\cdot 2^{z+2}\) 前面有 \(2\) 个 \(p_{j}\) 项,\(p_{i}\cdot 2^{k-1}\) 前面有 \(k-1-z\) 个 \(p_{j}\) 项。得到逆序数为 \(\frac{(k-z)(k-1-z)}{2}\)。

同理,假设 \(p_{j}\cdot 2^{z}<p_{i}\)(\(z\geq0\)),将对应的两行合并之后可以得到如下序列:

$$ p_{j}\cdot 2^{0},p_{j}\cdot 2^{1},\dots,p_{j}\cdot 2^{z},p_{i}\cdot 2^{0},p_{j}\cdot 2^{z+1},p_{i}\cdot 2^{1},\dots,p_{j}\cdot 2^{k-1},p_{i}\cdot 2^{k-z-1},\dots,p_{i}\cdot 2^{k-1} $$

可以发现,\(p_{i}\cdot 2^{k-z-1}\) 到 \(p_{i}\cdot 2^{k-1}\) 的逆序数都为 \(k\),总和为 \(k(z+1)\)。剩余部分的逆序数构成首项为 \(z+1\),尾项为 \(k-1\),公差为 \(1\) 的等差数列,总和为 \(\frac{(k+z)(k-1-z)}{2}\)。得到逆序数为 \(\frac{(k+z)(k-1-z)}{2}+k(z+1)\)。

如何降低时间复杂度?通过上述分析,可以知道对于任意 \(p_{i}\) 和 \(p_{j}\),它们之间的逆序数是由 \(z\) 决定的。也就是说,如果给定 \(p_{i}\) 和 \(z\)(\(z\) 为任意整数),对于任意满足 \(p_{i}\cdot 2^{z}<p_{j}<p_{i}\cdot 2^{z+1}\) 条件的 \(p_{j}\) 来说,\(p_{i}\) 和 \(p_{j}\) 之间的逆序数都是相同的。注意,当 \(z<0\) 时,对不等式变形得到 \(p_{i}\cdot 2^{-1}<p_{j}\cdot 2^{-z-1}<p_{i}\),此时的 \(z\) 和上面逆序数公式中的 \(z\) 不同,并且需要处理整数除法的舍入问题(存在一些边界情况)。

要快速求出区间中 \(p_{j}\) 的个数,可以使用树状数组/线段树,从而可以得到 \(O(n\log{\min(\log{n},k)}+k\log{k})\) 的解决方案。外层循环枚举 \(p_{i}\)(倒序遍历数组 \(p\),因为之前的讨论都基于 \(i<j\) 的假设),内层循环枚举 \(z\)(大小由 \(n\) 和 \(k\) 限制),然后使用树状数组求区间和,之后可以 \(O(1)\) 时间内计算出该区间的 \(p_{j}\) 和当前枚举的 \(p_{i}\) 之间的逆序数。

PS:还有另一种写法,只需要对树状数组的前缀求和,而不是区间求和,写起来好像更简单,但是没看懂。好难,溜了。

Codeforces Round 916 (Div. 3)

Three Activities

题目

输入长度为 \(n\) 的数组 \(a,b,c\),输出 \(a_{i}+b_{j}+c_{k}\) 的最大值,要求 \(i,j,k\) 互不相同。

数据范围:\(3\leq n\leq 10^{5}\),\(1\leq a_{i},b_{i},c_{i}\leq 10^{8}\)。

思路

  • 方法一:可以发现答案只和数组 \(a,b,c\) 中最大的三个元素有关,首先建立三个数组对应的下标数组,对其降序排序。然后使用三重循环暴力枚举前三个元素的组合,要求 \(i,j,k\) 互不相同,最后取组合的最大值作为答案。
  • 方法二:状压 DP,定义 \(dp[i][j]\) 表示 \([0,i]\) 范围内从 \(j\)(\(0\leq j\leq 7\))所对应数组中各取一个元素,能够得到的元素和的最大值。例如,当 \(j=3\) 时,表示从 \(a\) 和 \(b\) 中取元素。可以使用倒序枚举的方式优化空间。

Game with Marbles (Hard Version)

题目

输入长度为 \(n\) 的数组 \(a,b\),输出游戏结束时的得分 \(s\)。游戏内容为:玩家 \(A,B\) 每次可以选一个下标 \(i\),如果当前轮到玩家 \(A\),则进行 \(s=s+(a_{i}-1)\) 操作,否则进行 \(s=s-(b_{i}-1)\) 操作,不能重复选择同一个下标。游戏从玩家 \(A\) 开始,并且假设 \(A,B\) 双方都以最优的方式进行游戏。

数据范围:\(2\leq n\leq 2\cdot 10^{5}\),\(1\leq a_{i},b_{i}\leq 10^{9}\)。

思路

假设当前轮到玩家 \(A\),选择下标 \(i\),则答案会增加 \(a_{i}-1\),并且 \(b_{i}\) 将会无效化。相当于每次选择对答案的贡献为 \(a_{i}+b_{i}\),建立一个下标数组,按照该方式对数组降序排序。然后让玩家 \(A,B\) 依次选择数组中的下标,该游戏方式是最优的。

Educational Codeforces Round 160 (Rated for Div. 2)

Swap and Delete

题目

输入长度为 \(n\) 的二进制字符串,输出执行操作需要的最小成本,使得对于操作之后得到的字符串 \(t\) 的每个字符 \(t_{i}\),都有 \(t_{i}\neq s_{i}\),其中 \(1\leq i\leq |t|\)。有两种操作:

  • 从字符串 \(s\) 中删除一个字符,操作的成本为 \(1\)。
  • 交换字符串 \(s\) 中的两个字符,操作的成本为 \(0\)。

数据范围:\(1\leq n\leq 2\cdot 10^{5}\)。

思路

由于只有删除操作会导致成本增加,所以我们只需要让字符串 \(t\) 尽可能长就好。首先对字符串 \(s\) 中的 \(0\) 和 \(1\) 计数,然后贪心的构造字符串 \(t\),如果 \(s_{i}=0\),则 \(t_{i}=1\),反之亦然,直到不能增加长度为止。最后最小成本就是 \(n-|t|\)。

Game with Multiset

题目

输入一个整数 \(m\) 表示查询的次数,以及 \(m\) 行查询,每行包含两个整数 \(t_{i}\) 和 \(v_{i}\)。初始时你有一个空的多重集合(multiset):

  • 如果 \(t_{i}=1\),将元素 \(2^{v}\) 加入集合。(\(0\leq v_{i}\leq 29\))
  • 如果 \(t_{i}=2\),询问 \(v_{i}\) 是否可以表示为当前集合的某个子集之和,并输出 YES 或 NO。(\(0\leq v_{i}\leq 10^{9}\))

数据范围:\(1\leq m\leq 10^{5}\)。

思路

  • 方法一:使用数组对 \(t_{i}=1\) 的 \(v_{i}\) 计数,对于每个询问,从低到高遍历 \(v_{i}\) 的二进制 \(1\)。假设当前遍历到 \(2^{k}\),如果集合中存在 \(2^{k}\) 则当前位可以被表示(假设集合中有 \(c_{k}\) 个 \(2^{k}\)),并且集合中剩余的 \(2^{k}\) 可以合并为 \(\frac{c_{k}-1}{2}\) 个 \(2^{k+1}\),然后遍历下一位,这样最终可以判断 \(v_{i}\) 是否能被集合表示。
  • 方法二:使用数组对 \(t_{i}=1\) 的 \(v_{i}\) 计数,对于每个询问,从高到低遍历 \(v_{i}\) 的二进制 \(1\)。假设当前遍历到 \(2^{k}\),则执行 \(v_{i}=v_{i}-(\min{(v_{i}>>k,c_{k})}<<k)\) 操作(假设集合中有 \(c_{k}\) 个 \(2^{k}\)),表示将集合中的元素尽可能填补到 \(v_{i}\) 中,最终 \(v_{i}\) 还需要多少值,如果最终 \(v_{i}=0\) 则它可以被集合表示。

Array Collapse

题目

输入长度为 \(n\) 的数组 \(p\),其中的元素互不相同。输出执行任意次操作能够得到的不同数组个数,结果对 \(998244353\) 取余。每次操作可以选择 \(p\) 的一个子数组(假设为 \([i,j]\)),将子数组中除最小值之外的所有数删除。

数据范围:\(1\leq n\leq 3\cdot 10^{5}\),\(1\leq p_{i}\leq 10^{9}\)。

思路

  • 方法一:使用分治 + 线段树的时间复杂度为 \(O(n\log{n})\) 的思路,比赛时大概就是这个思路,不过想岔了一点。对于区间 \([i,j]\),使用线段树找到区间最小值的下标,然后左右分治求不同数组的个数,最后将两者相乘。这个方法还有一些特殊情况需要维护,具体看代码
  • 方法二:参考灵神视频单调栈优化 DP,将 \(f[i]\) 定义为以 \(p[i]\) 结尾的子序列个数,然后利用动态规划转移,最后将所有 \(f[i]\) 求和得到答案。单调栈这个规律有点难想,具体看视频吧。