393-【二分+容斥】【ST表/单调栈】【划分型DP】
二分 + 容斥
题目链接
https://leetcode.cn/problems/kth-smallest-amount-with-single-denomination-combination/description/
题目大意

题目思路
假设有一个 x 元硬币
- 思考只有一种面额为3的硬币时,3 可以组成不超过x的面额的数量有 x / 3 种!
- 思考有两种面额【3,6】,可以组成不超过x的面额数量有 x / 3 + x / 6 - x / lcm(3,6)种!
根据容斥原理可写出如下代码:
版本一【比赛时写的】
class Solution:
def findKthSmallest(self, coins: List[int], k: int) -> int:
lcms = [[]for _ in range(len(coins) + 1)]
lcms[1] = coins
lcms[-1] = [lcm(*coins)]
for i in range(2,len(coins)):
lst = list(combinations(coins,i))
for x in lst:
lcms[i].append(lcm(*x))
print(lcms)
def cal(x):
ans = 0
for i in range(1,len(coins) + 1):
tmp = 0
for y in lcms[i]:
tmp += x // y
## 容斥细节 是偶数就减,否则就加
if i % 2 != 0:
ans += tmp
else:
ans -= tmp
return ans
left = min(coins)
right = min(coins) * k
while left < right:
mid = left + right >> 1
if cal(mid) >= k:
right = mid
else:
left = mid + 1
return left
版本二【灵神版本】
class Solution:
def findKthSmallest(self, coins: List[int], k: int) -> int:
def check(m: int) -> bool:
cnt = 0
for i in range(1, 1 << len(coins)): # 枚举所有非空子集
lcm_res = 1 # 计算子集 LCM
for j, x in enumerate(coins):
if i >> j & 1:
lcm_res = lcm(lcm_res, x)
if lcm_res > m: # 太大了
break
else: # 中途没有 break
cnt += m // lcm_res if i.bit_count() % 2 else -(m // lcm_res)
return cnt >= k
return bisect_left(range(min(coins) * k), True, k, key=check)
单调栈/ST表
题目链接
https://leetcode.cn/problems/find-the-number-of-subarrays-where-boundary-elements-are-maximum/description/
题目大意

思路
- 使用ST表维护区间最值
- 枚举相同元素x对应的下标,判断query(left,right) = x,否则就要中止!
- 判断出连续满足条件的下标个数cnt,ans += cnt * (cnt + 1) // 2
版本一【ST表】
class Solution:
def numberOfSubarrays(self, nums: List[int]) -> int:
n = len(nums)
nums = [0] + nums
N = n + 10
M = int(log2(N)) + 1
st = [[0 for _ in range(M)] for _ in range(N)]
# st[i][j] 表 示 以 i 开 始,长 度 为 2^j 的 区 间 的 值 !
for i in range(1, n + 1):
st[i][0] = nums[i]
# 预 处 理 区 间 信 息
for j in range(1, M):
for i in range(1, n + 1):
length = 1 << (j - 1)
if i + length <= n:
# 区间长度为:2^j left:[i,i + 2^(j - 1) - 1] right:[i + 2^(j - 1),i + 2^(j - 1) + 2^(j - 1) - 1]
left, right = st[i][j - 1], st[i + length][j - 1]
st[i][j] = max(left, right)
else:
break
# 查 询 [l,r] 区 间 的 最 值
def query(l, r):
k = int(log2(r - l + 1))
# 区间长度为 2^k left:[l,l + 2^k - 1] right:[r - 2^k + 1,r]
left, right = st[l][k], st[r - (1 << k) + 1][k]
return max(left, right)
idx = defaultdict(list)
for i,x in enumerate(nums):
if i == 0:
continue
idx[x].append(i)
ans = 0
for x in idx:
lst = idx[x]
left = 0
while left < len(lst):
right = left
while right < len(lst):
if query(lst[left],lst[right]) == x:
right += 1
else:
break
cnt = right - left
ans += cnt * (cnt + 1) // 2
left = right
return ans
- 维护一个单调递减的栈,如果遇到一个元素x恰好大于栈顶元素y
- ① y不能与x后面的元素构成合法子数组
- ② x可能存在与y前面的元素构成合法子数组
- 技巧:添加哨兵栈顶节点
- 排除单个节点构成子数组的情况,因为单调栈考虑的两个元素之间的那段区间
- 当x找到与之对应的\(x_k\)后,将\(x_k\)对应的元素数量 + 1,为之后的累加做基础【可能,之后还会有\(x_1\)与 \(x_k\)对应】
版本二【单调栈】
class Solution:
def numberOfSubarrays(self, nums: List[int]) -> int:
ans = len(nums)
st = [[inf,0]]
for x in nums:
while x > st[-1][0]:
st.pop()
if x == st[-1][0]:
ans += st[-1][1]
st[-1][1] += 1
else:
st.append([x,1])
return ans
划分型DP
题目链接
https://leetcode.cn/problems/minimum-sum-of-values-by-dividing-array/description/
题目大意

没啥好说的,一种套路吧
dfs(i,j,val)表示到达下标为i的元素,已经分成j段了,且当前段的and值为val
之后考虑当前值是否可以划分,如果val 小于 andValue[j]那么就返回inf表示不合法
class Solution:
def minimumValueSum(self, nums: List[int], andValues: List[int]) -> int:
n,m = len(nums),len(andValues)
@cache
def dfs(i,j,val):
if m - j > n - i:
return inf
if j == m:
return 0 if i == n else inf
val &= nums[i]
if val < andValues[j]:
return inf
ans = dfs(i + 1,j,val)
if val == andValues[j]:
ans = min(ans,dfs(i + 1,j + 1,-1) + nums[i])
return ans
ans = dfs(0,0,-1)
return ans if ans < inf else -1

浙公网安备 33010602011771号