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)**