LeetCode1799.N 次操作后的最大分数和
目录
1799. N 次操作后的最大分数和
Difficulty: 困难
给你 nums
,它是一个大小为 2 * n
的正整数数组。你必须对这个数组执行 n
次操作。
在第 i
次操作时(操作编号从 1 开始),你需要:
- 选择两个元素
x
和y
。 - 获得分数
i * gcd(x, y)
。 - 将
x
和y
从nums
中删除。
请你返回 n
次操作后你能获得的分数和最大为多少。
函数 gcd(x, y)
是 x
和 y
的最大公约数。
示例 1:
输入:nums = [1,2]
输出:1
解释:最优操作是:
(1 * gcd(1, 2)) = 1
示例 2:
输入:nums = [3,4,6,8]
输出:11
解释:最优操作是:
(1 * gcd(3, 6)) + (2 * gcd(4, 8)) = 3 + 8 = 11
示例 3:
输入:nums = [1,2,3,4,5,6]
输出:14
解释:最优操作是:
(1 * gcd(1, 5)) + (2 * gcd(2, 4)) + (3 * gcd(3, 6)) = 1 + 4 + 9 = 14
提示:
- $1 <= n <= 7$
- $\text{nums.length} == 2 \ast n$
- $1 <= nums[i] <= 10^6$
解法一
48th 双周赛 t4,比较简单,看数据范围就知道是状压 DP,一开始写了个二维的状态 $dp[i][mask]$ ,包含了当前操作的次数 $i$ ,以及选取状态 $mask$ ,结果 t 了。忘记考虑 $\gcd$ 的复杂度了,后面对 $\gcd$ 做了预处理优化勉强过了,但是依然比较慢
重新思考了递推状态,发现当前操作次数这个状态其实是没有必要的,这个状态可以通过 $mask$ 的二进制 $1$ 的位数推断出来,每操作一次多两个 $1$ ,所以操作次数为 $\frac{\text{bit1}(mask)}{2}$ 。
所以状态变为 $dp[mask]$ ,代表经过 $\frac{\text{bit1}(mask)}{2}$ 次操作,选取的元素掩码为 $mask$ 时的最大得分
- 入口: $dp[0] = 0$
- 转移: $dp[mask] = \max(dp[mask],\frac{\text{bit1}(mask)}{2} \ast \gcd(j,k) + dp[mask\oplus(1«j)\oplus(1«k)])$ 枚举所有状态,然后枚举所有数对 $j,k$ ,确保 $mask$ 包含 $j,k$ ,然后递推取最大值
- 出口: $dp[1«m-1]$ ,时间复杂度 $O(2^m*m^2)$ , $m$ 为数组长度
代码实现如下:
import java.util.*;
class Solution {
public int maxScore(int[] nums) {
int len = nums.length;
int n = len/2;
int[] dp = new int[2<<len];
int[][] cache = new int[len][len];
for (int i = 0; i < len; i++) {
for (int j = i+1; j < len; j++) {
cache[i][j] = gcd(nums[i], nums[j]);
}
}
for (int s = 0; s < (1<<len); s++) {
int cnt = countBit(s);
// 选取个数一定是偶数
if ((cnt&1)==1) continue;
for (int j = 0; j < len; j++) {
//s 必须选取 j, k
if (((s>>>j)&1)==0) continue;
for (int k = j+1; k < len; k++) {
if (((s>>>k)&1)==0) continue;
dp[s] = Math.max(dp[s], (countBit(s)/2)*cache[j][k] + dp[s^(1<<j)^(1<<k)]);
}
}
}
return dp[(1<<len)-1];
}
public int countBit(int a) {
int cnt = 0;
while (a != 0) {
a = a&(a-1);
cnt++;
}
return cnt;
}
public int gcd(int a, int b){
if(b==0) return a;
return gcd(b, a%b);
}
}
解法二
题解区看到的另一种写法,直接枚举 $\text{mask}$ 的子集,思路挺好,但是时间复杂度略高, $O(2^{2n} \ast 2^k)$ , $k$ 是掩码中 $1$ 的个数,最多可能是 $2*n$
public int maxScore2(int[] nums) {
int len = nums.length;
int n = len/2;
int[] dp = new int[2<<len];
for (int i = 0; i < len; i++) {
for (int j = i+1; j < len; j++) {
dp[(1<<i)|(1<<j)] = gcd(nums[i], nums[j]);
}
}
//2^len
for (int s = 0; s < (1<<len); s++) {
//2^k k 是 s 中 1 的个数
for (int i = s; i != 0; i=(i-1)&s) {
if (countBit(s)-countBit(i)==2) {
dp[s] = Math.max(dp[s], countBit(s)/2*dp[s^i]);
}
}
}
return dp[(1<<len)-1];
}