diff --git a/dp/line.md b/dp/line.md index 42dda47..e859278 100644 --- a/dp/line.md +++ b/dp/line.md @@ -5006,6 +5006,65 @@ public: * * * +> [!NOTE] **[LeetCode 2944. 购买水果需要的最少金币数](https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/)** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 线性递推 状态定义与转移 + +
+详细代码 + + +##### **C++** + +```cpp +class Solution { +public: + // 分析题意... 购买 idx 位置处的水果,接下来可以免费获得 idx 个(而非 prices[idx] 个)其他水果 + // [且下标 idx 从 1 开始] + // + // 以及... 按照数据示例,【接下来】值得就是一段连续区间... 不能自由选择 + // => 考虑区间 dp => O(n^3) 显然不太可行 + // => 考虑线性 dp + // + // 从前到后 所有水果必然是连续获得 则可以一维线性维护 "截止到前 i 个位置的最小开销" + + const static int N = 1010; + + int f[N]; + + int minimumCoins(vector& prices) { + memset(f, 0x3f, sizeof f); + int n = prices.size(); + f[0] = 0; + for (int i = 1; i <= n; ++ i ) { + int buy_cost = f[i - 1] + prices[i - 1]; + f[i] = min(f[i], buy_cost); // 第 i 个位置买的情况下,更新 f[i] + // 同时更新 f[i+j] + for (int j = 1; j <= i && i + j <= n; ++ j ) + f[i + j] = min(f[i + j], buy_cost); + } + return f[n]; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * + ### 复杂递推 #### 数学递推 dp diff --git a/dp/opt/monotonous-queue-stack.md b/dp/opt/monotonous-queue-stack.md index 7055e4b..e69f863 100644 --- a/dp/opt/monotonous-queue-stack.md +++ b/dp/opt/monotonous-queue-stack.md @@ -1277,6 +1277,101 @@ int main() { * * * +> [!NOTE] **[LeetCode 2945. 找到最大非递减数组的长度](https://leetcode.cn/problems/find-maximum-non-decreasing-array-length/)** [TAG] +> +> 题意: TODO + +> [!TIP] **思路** +> +> 单调队列优化 DP 重点在于推导证明 +> +> 重复做 +> +> > 反向题目: https://leetcode.cn/problems/minimum-replacements-to-sort-the-array/ + +
+详细代码 + + +##### **C++** + +```cpp +class Solution { +public: + // 定义 f[i] 表示操作前 i 个数得到的 "最长非递减数组" 的长度 + // 同时使用 g[i] 记录在 f[i] 尽可能最大的情况下,该区间操作后的最后一个数的【最小值】 + // 【因为末尾的这个值越小,对后面的计算结果越有利】 + // + // 则 if-condition: sums[j + 1] + ... + nums[i] >= g[j] + // then: f[i] = max_j{f[j]} + 1 + // 将该 if-condition 做转化: s[i] - s[j] >= g[j] + // => s[i] >= s[j] + g[j] + // 最终得到 f[i] = max{f[j]} + 1 (j 满足 s[j] + g[j] <= s[i]) + // + // 又因为 推理可知【要让g[i] 最小 则f[j1,j2,j3]相同的情况下j要尽可能大】 + // => 考虑如果 j1& nums) { + int n = nums.size(); + { + s[0] = 0; + for (int i = 1; i <= n; ++ i ) + s[i] = s[i - 1] + nums[i - 1]; + } + + // 显然可以从 idx-0 转移过来 + f[0] = 0, g[0] = 0; + hh = 0, tt = -1; + q[ ++ tt] = 0; + + for (int i = 1; i <= n; ++ i ) { + // 弹出非最优的队首 + // - 注意是hh+1 + // - 注意比较规则 + while (hh + 1 <= tt && calc(q[hh + 1]) <= s[i]) { + // ATTENTION 如果队首后面有比队首更优的,弹出当前队首 + //【本质就是满足条件的就可以,而且下标越大越优】 + hh ++ ; + } + + f[i] = f[q[hh]] + 1; + g[i] = s[i] - s[q[hh]]; + + // 弹出非最优的队尾 + while (hh <= tt && calc(q[tt]) >= calc(i)) + tt -- ; + q[ ++ tt] = i; + } + return f[n]; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * + ### 决策单调性优化 > TODO: https://www.luogu.com.cn/training/9352 Part 4.9 斜率优化动态规划 diff --git a/topic/bf-improve.md b/topic/bf-improve.md index f7728db..42d514c 100644 --- a/topic/bf-improve.md +++ b/topic/bf-improve.md @@ -1495,3 +1495,171 @@ public:
* * * + +> [!NOTE] **[LeetCode 2949. 统计美丽子字符串 II](https://leetcode.cn/problems/count-beautiful-substrings-ii/) [TAG]** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 难点在于思路梳理 以及索引组织 +> +> 结合数学分析推导最高效的做法 +> +> 重复做 + +
+详细代码 + + +##### **C++ 基础 O(n*sqrt(k))** + +```cpp +class Solution { +public: + // 考虑记录元音字母的数量及对应的位置 则辅音可求 + // + // 考虑: 以每一个位置为结尾 往前数能找到的合法区间 + // => 问题在于 合法区间怎么确定 + // 假定 [i, j] 合法, 则 + // - 长度为偶数 j-i+1 % 2 == 0 => 下标奇偶分类可解 => 在后面2.的条件下不需要分类 + // - 元音个数与偶数相同 + // -> 1. 按个数是一半 则需要遍历校验 + // -> 2. 按个数相同【记录diff 取模】 可以快速找到所有符合要求的 再判断下能否整除就好做了 + + using LL = long long; + const static int N = 5e4 + 10; + + // unordered_set S = {'a', 'e', 'i', 'o', 'u'}; + bool check(char c) { + return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; + } + + // vector f[N + N]; // 某个diff下,下标是多少 + unordered_map> f; // 某个diff下,记录元音个数对k取模的次数【后面推导】 + // unordered_map f[N + N] => 会超时 改成两层嵌套 + int sum[N]; + + long long beautifulSubstrings(string s, int k) { + // for (int i = 0; i < N + N; ++ i ) + // f[i].clear(); + // f[0 + N].push_back(0); + f[0][0 % k] = 1; + memset(sum, 0, sizeof sum); + + vector wd2; // w divide 2 + for (int i = 0; i < k; ++ i ) { // ATTENTION: 考虑只枚举到 k, 但是需要从 0 开始枚举 + if (i * i % k == 0) + wd2.push_back(i); + } + + LL res = 0; + int n = s.size(); + for (int i = 1, d = 0; i <= n; ++ i ) { + // bool flag = S.count(s[i - 1]); + bool flag = check(s[i - 1]); + d += flag ? 1 : -1; + sum[i] = sum[i - 1] + flag; + + // xs 中都是 diff 相同的,满足个数与长度要求 + auto & xs = f[d]; + + // 【ATTENTION 循环对于该数据范围太过重型 考虑是否有更快速索引办法】 + // for (auto x : xs) { + // // l: x+1, r: i + // int w = (i - x) / 2; + // if (w * w % k == 0) + // res ++ ; + // } + // + // 考虑 w*w%k == 0 <=> (w/2%k * w/2%k) % k == 0 + // ==> 思考【w/2必然是k的倍数?】不 => 因为 /2 的影响 + // ==> 又因为k不定 求逆元比较困难 考虑直接枚举可行的 w/2 记为wd2 + // 此时: 如果使用 i-2*x 去枚举左端点,复杂度仍然很高 + // + // 考虑如何避免枚举: 重新定义 w = s[r]-s[l] 其也是长度的一半 同时 w*w%k == 0 (w%k==0) + // 则 所有可行的l 都能使得 (s[l]-s[r])%k == 0 + // => s[l]%k == s[r]%k + // => 同余 则 f 的第二维按照模数维护即可 + for (auto x : wd2) { + int t = (sum[i] - x + k) % k; + if (xs.count(t)) + res += xs[t]; + } + + xs[sum[i] % k] ++ ; + } + + return res; + } +}; +``` + +##### **C++ 数学 O(n)** + +```cpp +class Solution { +public: + // 前面已经推导知: 假定长度为 w, 有 (w/2)*(w/2)%k == 0 + // + // 0x3f 的思路: 不是转换逆元, 而是把 /2 提取出来 => w*w % (4*k) == 0 + // TRICK => 数学思维: 假定 K=4*k, 而 K 由 p1^e1 * p2^e2 ... 组成 + // 则: w = p1^((e1+1)/2) * ... 【幂次除二上取整即可】 + // + // 接下来 w 必须是新的 k 的倍数 + // (r - l) % k == 0 => r % k == l % k + // 按照下标进行分组计数 且需要保证 diff 相同(使用状态压缩实现) + // + // 代码实现: 把 {下标分组, 状态表示} 合并成一维表示了 + + using LL = long long; + const static int N = 5e4 + 10; + + int p_sqrt(int x) { // x 不会超过 4000 + int res = 1; + for (int i = 2; i * i <= x; ++ i ) { + // 计算 x 包含多少个 i 的幂次 + int t = 0; + while (x % i == 0) + x /= i, t ++ ; + + for (int j = 0; j < (t + 1) / 2; ++ j ) + res *= i; + } + if (x > 1) + res *= x; // 细节 + return res; + } + + // a + e + i + o + u + constexpr static int AEIOU_MASK = (1 << 0) + (1 << 4) + (1 << 8) + (1 << 14) + (1 << 20); + + long long beautifulSubstrings(string s, int k) { + int n = s.size(); + k = p_sqrt(k * 4); + + unordered_map f; + LL res = 0, sum = N; // sum=N而非sum=0 是因为其值可能为负数 为了能用int表示而做的偏移 + f[0 << 17 | sum] ++ ; // 插入 f[{0, N}] N是偏移量 + for (int i = 1; i <= n; ++ i ) { + int bit = (AEIOU_MASK >> (s[i - 1] - 'a')) & 1; + sum += bit ? 1 : -1; + res += f[(i % k) << 17 | sum] ++ ; + } + return res; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * \ No newline at end of file diff --git a/topic/trick.md b/topic/trick.md index 385fc6b..9b3e599 100644 --- a/topic/trick.md +++ b/topic/trick.md @@ -6012,6 +6012,64 @@ public: * * * +> [!NOTE] **[LeetCode 2943. 最大化网格图中正方形空洞的面积](https://leetcode.cn/problems/maximize-area-of-square-hole-in-grid/)** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 横竖相互独立 + +
+详细代码 + + +##### **C++** + +```cpp +class Solution { +public: + // 【横竖互不影响】 所以可以各自找到横着的最大 & 竖着的最大 + // => 但是题目要的还是正方形,所以只能找到出现过的所有的记录下来 + // => 又因为连续性 只记录最大的即可 + + int get(int n, vector & ns) { + n += 2; + sort(ns.begin(), ns.end()); + + int ret = 0; + for (int i = 0; i < ns.size(); ++ i ) { + int j = i + 1; + while (j < ns.size() && ns[j] == ns[j - 1] + 1) + j ++ ; + // 获取连续断开的区间长度 + int w = j - i; + ret = max(ret, w + 1); + i = j - 1; + } + return ret; + } + + int maximizeSquareHoleArea(int n, int m, vector& hBars, vector& vBars) { + int x = get(n, hBars), y = get(m, vBars); + return min(x, y) * min(x, y); + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * + ### 根据题意简化思维 > [!NOTE] **[Codeforces C. Dima and Staircase](https://codeforces.com/problemset/problem/272/C)**