Skip to content

Commit

Permalink
add: contest biweekly 118 & weekly 373
Browse files Browse the repository at this point in the history
  • Loading branch information
binacs committed Dec 2, 2023
1 parent fdffc34 commit 79b0819
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 0 deletions.
59 changes: 59 additions & 0 deletions dp/line.md
Original file line number Diff line number Diff line change
Expand Up @@ -5006,6 +5006,65 @@ public:

* * *

> [!NOTE] **[LeetCode 2944. 购买水果需要的最少金币数](https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/)**
>
> 题意: TODO

> [!TIP] **思路**
>
> 线性递推 状态定义与转移

<details>
<summary>详细代码</summary>
<!-- tabs:start -->

##### **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<int>& 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
```

<!-- tabs:end -->
</details>

<br>

* * *

### 复杂递推

#### 数学递推 dp
Expand Down
95 changes: 95 additions & 0 deletions dp/opt/monotonous-queue-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
<details>
<summary>详细代码</summary>
<!-- tabs:start -->

##### **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<j2 且都满足转移条件,则必然选j2【思考】

// 考虑单调队列动态维护 j 的取值范围,使得队首最优即可

using LL = long long;
const static int N = 1e5 + 10;

LL s[N], f[N], g[N];
int q[N], hh, tt;

LL calc(int x) {
return s[x] + g[x];
}

int findMaximumLength(vector<int>& 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
```

<!-- tabs:end -->
</details>

<br>

* * *

### 决策单调性优化

> TODO: https://www.luogu.com.cn/training/9352 Part 4.9 斜率优化动态规划
Expand Down
168 changes: 168 additions & 0 deletions topic/bf-improve.md
Original file line number Diff line number Diff line change
Expand Up @@ -1495,3 +1495,171 @@ public:
<br>

* * *

> [!NOTE] **[LeetCode 2949. 统计美丽子字符串 II](https://leetcode.cn/problems/count-beautiful-substrings-ii/) [TAG]**
>
> 题意: TODO

> [!TIP] **思路**
>
> 难点在于思路梳理 以及索引组织
>
> 结合数学分析推导最高效的做法
>
> 重复做

<details>
<summary>详细代码</summary>
<!-- tabs:start -->

##### **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<char> S = {'a', 'e', 'i', 'o', 'u'};
bool check(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

// vector<LL> f[N + N]; // 某个diff下,下标是多少
unordered_map<int, unordered_map<int, int>> f; // 某个diff下,记录元音个数对k取模的次数【后面推导】
// unordered_map<int, int> 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<int> 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<int, int> 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

```

<!-- tabs:end -->
</details>

<br>

* * *
Loading

0 comments on commit 79b0819

Please sign in to comment.