1799. N 次操作后的最大分数和
题目描述
给一个数组正整数nums,数组长度是2*n
规定需要对数组执行n次操作,每次操作从nums中拿出两个元素x和y,计算分数\(i \cdot gcd(x,y)\)
问n次操作后的分数和最大是多少?
| f1-状态压缩+动态规划 |
基本分析
- 有没有贪心的可能?没有啥思路
- 如果考虑用dp实现,怎么定义状态?枚举当前的选择为mask(对mask有偶数1的限制),定义f[mask]为当前选择是mask的时候的最大分数和。
- 基于以上dp的定义,考虑怎么转移?找到mask中1的位置,通过双层循环实现补充不漏的去掉其中2个1,mask的状态可以从以上状态中转移过来。具体地说,加入去掉的1的位置是j和k,那么f[mask] = f[i - (1<<j) - (1<<k)] + gcd[j, k] * 当前操作次数。而操作次数在i中可以体现出来(1的个数/2)
- 有没有预处理的需求?因为可能需要多次枚举某两个位置的最大公约数,可以定义m*m的二维数组,把结果存起来
5.dp的边界条件怎么考虑?当mask取0的时候,最大的分就是0,f[0] = 0
代码
class Solution:
def gcd(self, x, y):
if y == 0:
return x
else:
return self.gcd(y, x%y)
def maxScore(self, nums: List[int]) -> int:
m = len(nums)
# 预处理放最大公约数的值
gcd = [[0] * m for _ in range(m)]
for i in range(m-1):
for j in range(i+1, m):
gcd[i][j] = self.gcd(nums[i], nums[j])
mask = 1<<m
f = [0] * mask
for i in range(mask):
bits = bin(i).count('1')
if bits % 2 == 1:
continue
for j in range(m-1):
if i & (1<<j) == 0:
continue
for k in range(j+1, m):
if i & (1<<k) == 0:
continue
f[i] = max(f[i], f[i - (1<<j) - (1<<k)] + int(gcd[j][k] * bits/2))
return f[-1]
复杂度
时间:假设值的范围是n,数组长度是m,存公约数需要\(O(m^2 \cdot log(n)), dp过程需要O(m^2 \cdot 2^m)\)
空间:存公约数数组需要\(O(m^2)\), 存dp结果需要\(O(2^m)\)
总结
直接枚举子集和利用dp处理有啥区别?直接枚举会有很多重复计算,dp做法会利用到之前的一些结果,从这个题中看,mask可能由最多m*m个之前的状态之一转移过来。
求公约数:这里用到辗转的方法,需要记下来;另外由于转移可能会频繁使用,这里用了预处理的方式
枚举mask中2个1的位置:为了保证不重复和不漏掉,对2个位置的顺序尽心了规定,从而通过双层循环可以实现
次数系数的获取:由于是枚举mask,mask中包含了次数的信息

浙公网安备 33010602011771号