T4. 搬砖
给定 \(n\) 个非负整数 \(a_i\),每次可以花费 \(1\) 的代价使得一个数字加 \(1\) 或者减 \(1\)(不能被减到负数),问最少需要多少代价使得所有数字的异或值为 \(0\) 。
要求多测
限制:
- 对于 \(5\%\) 的数据:\(n=2, a_i \leqslant 10^9\)
- 对于另外 \(10\%\) 的数据:\(n \leqslant 5\),\(a_i \leqslant 30\)
- 对于另外 \(15\%\) 的数据:\(n \leqslant 15\),\(a_i \leqslant 10^3\)
- 对于另外 \(20\%\) 的数据:\(n \leqslant 8\),\(a_i \leqslant 10^9\)
- 对于另外 \(15\%\) 的数据:\(n \leqslant 10\),\(a_i \leqslant 10^9\)
- 对于所有数据:\(1 \leqslant T \leqslant 6, 1 \leqslant n \leqslant 15, 0 \leqslant a_i \leqslant 10^9\)
参考难度:蓝
算法分析
下文中,假定 \(m = \max(a_i)\)
\(5\) 分:
此时 \(n = 2\),问题变为了使得两数相等的最小代价,输出两数的差值即可。
\(15\) 分(另外 \(10\%\) 的数据):
此时 \(n \leqslant 5\),\(a_i \leqslant 30\),考虑直接 \(dfs\) 暴力枚举每个 \(a_i\) 的最终取值 \(a_i'\),那么对于每一种情况所需要付出的代价就是 \(|a_i - a_i'|\) 之和,即 \(\sum |a_i - a_i'|\) 。取所有情况中的最小代价即可,时间复杂度为 \(O(m^n)\) 。
\(30\) 分(另外 \(15\%\) 的数据):
此时 \(n \leqslant 15\),\(a_i \leqslant 10^3\),原问题是“使得 \(n\) 个数字的异或值为 \(0\)”,那么当我们枚举了 \(a_n\) 的最终取值 \(a_n'\) 之后,问题变为了“使得前 \(n-1\) 个数字的异或值为 \(a_n'\)”。不难发现这是一个子问题,因此可以考虑动态规划。
定义 dp[i][j] 表示使得前 \(i\) 个数字的异或值为 \(j\) 的最小代价,枚举 \(im, j\) 后,再枚举 \(k\) 表示当前第 \(i\) 个数字的最终取值,那么可以得到转移方程:
最终的答案为 \(dp[n][0]\),总时间复杂度为 \(O(nm^2)\) 。
\(65\) 分(另外 \(35\%\) 的数据):
此时 \(a_i \leqslant 10^9\),依赖于枚举值域的做法显然不行。由于 \(n \leqslant 10\) 很小,且每个数字相比最终的结果只有不变、变小和变大这 \(3\) 种可能性。那么所有数字的变化情况组合在一起一共只有 \(3^n = 59049\) 种可能性,因此可以考虑状态压缩,将每个数字的变化情况压缩成一个三进制的数字 \(S\) 。
具体地,令 \(0, 1, 2\) 分别表示不变、变小、变大。那么 \(S = (211002)_3\) 表示第 \(2,3\) 个数字不变,第 \(4,5\) 个数字变小,第 \(1,6\) 个数字变大。
再注意到进行按位异或时,每一位之间是相互不影响的,结合上述的“用状态压缩去表示变化情况”的想法,我们可以从高位到低位去确定每一位最终的异或值为 \(0\) 的最小代价即可。
至于为什么是从高到低而非从低到高?这是因为所有低位的二次幂之和必然都小于高位的某一位的值,我们确定好高位相比于之前是变大还是变小之后,无论之后的低位取 \(0\) 还是 \(1\),都不会改变原本已经确定好的变大或者变小的状态。这样一来,我们只需要知道当前的 \(S\) 是多少,就可以知道每个数字的每一位取 \(0\) 或者 \(1\) 时所需要付出的代价。
至此,可以考虑状压dp,去按位计算最终的最小代价。定义 dp[i][j][S][0/1] 表示当前考虑到了第 \(i\) 位,确定了这一位的前 \(j\) 个数字,所有数字的变化状态为 \(S\),这一位的异或和为 \(0/1\) 对应的最小代价。枚举 \(i, j, S\),并考虑 \(a_j\) 的第 \(i\) 位取 \(0/1\),设 \(T\) 为原本的数字变化状态,设 \(v\) 为原本 \(a_j\) 的第 \(i\) 位的值。可以分为以下三类进行转移:
- 原本的 \(a_j\) 不变:
- 这一位取 \(0\),若 \(v=0\)则 \(a_j\) 不变,若 \(v=1\) 则 \(a_j\) 变小:
- 这一位取 \(1\),若 \(v=0\) 则 \(a_j\) 变大,若 \(v=1\) 则 \(a_j\) 不变
- 原本的 \(a_j\) 已经变小过了:
- 这一位取 \(0\),若 \(v=0\) 则代价不变,若 \(v=1\) 则代价增加:
- 这一位取 \(1\),若 \(v=1\) 则代价不变,若 \(v=0\) 则代价减少:
- 原本的 \(a_j\) 已经变大过了:
- 这一位取 \(0\),若 \(v=0\) 则代价不变,若 \(v=1\) 则代价减少:
- 这一位取 \(1\),若 \(v=1\) 则代价不变,若 \(v=0\) 则代价增加:
值得注意的是,上述的所有转移方程中,当 \(j=1\) 时,应当从 \(i-1\) 且 \(j=n\) 转移过来。
答案为:枚举 \(S\) 后取最小的 \(dp[i][n][S][0]\) 。
总时间复杂度为 \(T(Tn3^n\log m)\) 。
\(100\) 分:
不难发现 \(O(3^n)\) 的复杂度在于我们需要记录每个数字是变大还是变小,从而来判断每一位从 \(0 \to 1\) 或者 \(1 \to 0\) 时,到底是增加代价还是减少代价。若要降低这部分的复杂度,难点在于如何构造出一种方法来将数字变小和变大情况的代价计算进行合并,下文中用每一位的 \(x\) 代表 \(0/1\) 中的任意一个值。
先考虑数字变小的情况,假设当前某个数字 \(a_j\) 从第 \(i\) 位到第 \(0\) 位的值形如 \((1xxxxx)_2\),那么如果我们希望将它第 \(i\) 位的 \(1\) 改为 \(0\) 从而使得第 \(i\) 位整体的异或值为 \(0\),有两种变化方式:
- \((1xxxxx)_2 \to (0xxxxx)_2\),那么根据 \(65\) 分的做法,我们能够知道之后的每一位需要根据是 \(1 \to 0\) 还是 \(0 \to 1\) 来决定增加还是减少代价。
- \((0xxxxx)_2 \to (100000)_2 \to (1xxxxx)_2\),第 \(i\) 位变小后,会先经过后面位全是 \(0\) 的状态,此时如果我们希望继续变成 \((1xxxxx)_2\) 的任意状态,那么需要在之后遍历后面位的过程中将若干个 \(1 \to 0\) 即可,这样一来代价只会是增加的。
再考虑 \(a_j\) 的值变大的情况,设第 \(i\) 位到第 \(0\) 位的值形如 \((0xxxxx)_2\),那么将第 \(i\) 位的 \(0\) 变为 \(1\) 同样有两种变化方式:
- \((0xxxxx)_2 \to (1xxxxx)_2\),之后的每一位需要根据是 \(0 \to 1\) 还是 \(1 \to 0\) 来决定增加还是减少代价。
- \((0xxxxx)_2 \to (100000)_2 \to (1xxxxx)_2\),第 \(i\) 位变大后,会先经过后面全是 \(0\) 的状态,此时如果我们希望继续变成 \((1xxxxx)_2\) 的任意状态,那么只需要在之后遍历后面位的过程中将若干个 \(0 \to 1\) 即可,这样一来代价也只会是增加。
如此一来,通过 \((1xxxxx)_2 \to (011111)_2\) 以及 \((0xxxxxx)_2 \to (100000)_2\) 的变换,我们能够让数字 \(a_j\) 在之后的每一位需要变化的时候,都需要考虑增加代价,而不需要考虑减少代价;这样做等价于将变小和变大两种情况的代价计算方式,都合并为“增加代价”这一种情况了。于是可以将原本的三进制状态 \(S\) 改为二进制状态,用来记录每个数字是否发生变化,而无需记录具体时变小还是变大。
值得注意的是,当一个数字在第 \(i\) 位发生 \((1xxxxx)_2 \to (011111)_2\) 时,相当于给后面第 \([i-1, 0]\) 的每一位的整体异或值进行了 \(\oplus 1\);又因为异或偶数次 \(1\) 并不会改变整体的异或值,因此我们需要再给原本的 \(dp\) 数组多定义一个状态 \([0/1]\) 用来表示当前所有发生变化的数字,给后面每一位提前贡献了多少的异或值(\(0\) 或 \(1\))。
至此,定义 dp[i][j][S][0/1][0/1] 表示当前考虑到了第 \(i\) 位,确定了这一位的前 \(j\) 个数字,所有数字的变化状态为 \(S\),这一位的异或和为 \(0/1\),已经变化的数字贡献的每位异或值为 \(0/1\),对应的最小代价。设 \(T\) 为原本的数字变化状态,设 \(v\) 为原本 \(a_j\) 第 \(i\) 位的值,则转移如下:
- 考虑 \(a_j\) 原本没有变化,在当前第 \(i\) 位仍然不发生变化,那么代价为 \(0\) 。
- 考虑 \(a_j\) 原本没有变化,在当前第 \(i\) 位发生变化。若 \(v=0\),则代价 \(cost = 2^i - (a_j \& (2^i - 1))\);若 \(v = 1\),则代价 \(cost = 1 + (a_j \& (2^i-1))\)
- 考虑 \(a_j\) 发生过变化,且不改变当前第 \(i\) 位的值,那么代价为 \(0\) 。注意此时 \(a_j\) 这一位的值已经在之前变化高位的时候算入了 \(l\) 的值里面,不需要再对 \(k\) 进行异或 \(v\) 。
- 考虑 \(a_j\) 发生过变化,且改变当前第 \(i\) 位的值,那么代价为 \(2^i\) 。注意此时改变了 \(a_j\) 这一位的值,需要对 \(k\) 进行异或 \(1\) 。
转移的其他细节见 \(65\) 分做法,这里再补充:当第 \(i\) 位都处理好之后,在处理第 \(i-1\) 位之前,需要令 \(dp[i][n][S][1][1] = dp[i][n][S][0][1]\),然后将 \(dp[i][n][S][1][0]\) 和 \(dp[i][n][S][0][1]\) 变为 \(\infty\) 。这表示处理到第 \(i\) 位为止,如果已经发生改变的数字给后面位的异或值产生了 \(1\) 的贡献,那么在处理后面位的时候,异或值的初始值应当是 \(1\);反之,初始值为 \(0\) 。
答案为:枚举 \(S\) 后取最小的 \(\min(dp[0][n][S][0][0], dp[0][n][S][1][1])\) 。
总时间复杂度为 \(O(n2^n\log m)\) 。
浙公网安备 33010602011771号