393-【二分+容斥】【ST表/单调栈】【划分型DP】

二分 + 容斥

题目链接
https://leetcode.cn/problems/kth-smallest-amount-with-single-denomination-combination/description/
题目大意

image

题目思路

假设有一个 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/
题目大意
image
思路

  • 使用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/

题目大意

image

没啥好说的,一种套路吧

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
posted @ 2024-04-14 18:48  gebeng  阅读(24)  评论(0)    收藏  举报