From 9fca3ab7c9625428414ae6e2bc25a311ecf0e7a4 Mon Sep 17 00:00:00 2001 From: binacs Date: Sat, 20 Jul 2024 23:12:04 +0800 Subject: [PATCH] add: contest weekly 389&390, biweekly 127 --- basic/greedy.md | 111 +++++++++++++++++++++++++++++++++++++ dp/misc.md | 92 +++++++++++++++++++++++++++++++ string/trie.md | 108 ++++++++++++++++++++++++++++++++++++ topic/prepare.md | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 452 insertions(+) diff --git a/basic/greedy.md b/basic/greedy.md index b651262..ac3abe4 100644 --- a/basic/greedy.md +++ b/basic/greedy.md @@ -1469,6 +1469,117 @@ public: * * * +> [!NOTE] **[LeetCode 3085. 成为 K 特殊字符串需要删除的最少字符数](https://leetcode.cn/problems/minimum-deletions-to-make-string-k-special/)** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 重在理解贪心推导思路 +> +> 1. 一个非常显然的思路是直接枚举最终收敛的数值区间 +> +> 2. 贪心推理:下界 (而非上界) 必然出现在所有 cnt 中 +> +> 思考推理 (因为如果下界处在中间位置,对于更大的区间来说向下收缩是没有意义的) +> +> 进而,在数据范围更大时可以双指针进一步优化 (略) + +
+详细代码 + + +##### **C++ bf** + +```cpp +class Solution { +public: + int minimumDeletions(string word, int k) { + int n = word.size(); + vector xs; + { + int c[26]; + memset(c, 0, sizeof c); + for (auto x : word) + c[x - 'a'] ++ ; + + for (int i = 0; i < 26; ++ i ) + if (c[i]) + xs.push_back(c[i]); + sort(xs.begin(), xs.end()); + } + + int m = xs.size(); + + int res = n; + for (int i = 1; i <= 1e5 + 1; ++ i ) { + int r = i, l = max(0, r - k); + + int t = 0; + for (auto x : xs) + if (x < l) + t += x; + else if (x > r) + t += x - r; + res = min(res, t); + } + return res; + } +}; +``` + +##### **C++ gready** + +```cpp +class Solution { +public: + int minimumDeletions(string word, int k) { + int n = word.size(); + vector xs; + { + int c[26]; + memset(c, 0, sizeof c); + for (auto x : word) + c[x - 'a'] ++ ; + + for (int i = 0; i < 26; ++ i ) + if (c[i]) + xs.push_back(c[i]); + sort(xs.begin(), xs.end()); + } + + int m = xs.size(); + + int res = n; + for (auto x : xs) { + int l = x, r = x + k; + int t = 0; + for (auto v : xs) + if (v < l) + t += v; + else if (v > r) + t += v - r; + res = min(res, t); + } + + return res; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * + ### 排序不等式 > [!NOTE] **[AcWing 1395. 产品处理](https://www.acwing.com/problem/content/1397/)** diff --git a/dp/misc.md b/dp/misc.md index 98faaca..c1cb4f2 100644 --- a/dp/misc.md +++ b/dp/misc.md @@ -1246,4 +1246,96 @@ int main() {
+* * * + +### 状态转移方式 + +> [!NOTE] **[LeetCode 3098. 求出所有子序列的能量和](https://leetcode.cn/problems/find-the-sum-of-subsequence-powers/)** [TAG] +> +> 题意: TODO + +> [!TIP] **思路** +> +> 如果将所有差值离散化 统一求复杂度会爆掉 前缀和优化也无效 +> +> 考虑随用随算 + +> 1. 借助 trick 的转移方式实现 +> +> 2. 更 general 的前后缀分解,计算过程必须正确消除不可行情况 +> +> 解决办法 => **求差值+初始化** +> +> 重复做 + +
+详细代码 + + +##### **C++** + +```cpp +class Solution { +public: + // 长度等于k... + // 1. 排序 显然不影响结果 + // 2. 枚举差值发生的两个下标对 则中间元素都不能被选 且两侧也存在部分不能选(以某个位置为端点 左侧差值不小于x 总共有多少种方案) + // 3. 去重.. 两侧区分对待即可 【经典思维】 + // + // 长度恰好为k怎么解决? => 确定性状态 作为dp定义其中一个维度 + // + // 【考虑 结合数据范围】 + // ls[i][j][k] 左侧到i的位置 长度为j 差值不小于k 的所有方案数 + // + // 【重要:状态转移方式 + 复杂度分析】 + + using LL = long long; + const static int N = 55, MOD = 1e9 + 7; + const int INF = 0x3f3f3f3f; + + int n; + vector ns; + + + int sumOfPowers(vector& nums, int k) { + this->ns = nums; + this->n = ns.size(); + sort(ns.begin(), ns.end()); + + unordered_map f[N][N]; // k 作为离散 hash + + for (int i = 1; i <= n; ++ i ) { + f[i][1][INF] = 1; + for (int j = 2; j <= k; ++ j ) + // ATTENTION 第三维并非枚举所有差值可能,而是只枚举一定可能出现的,也即枚举前面的数字 + for (int last = 1; last < i; ++ last ) { + // ATTENTION trick + for (auto & [d, cnt] : f[last][j - 1]) { + LL nd = min(d, ns[i - 1] - ns[last - 1]); // trick + f[i][j][nd] = (f[i][j][nd] + cnt) % MOD; + } + } + } + + LL res = 0; + for (int i = 1; i <= n; ++ i ) + for (auto & [d, cnt] : f[i][k]) + res = (res + d * cnt % MOD) % MOD; + + return res; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ * * * \ No newline at end of file diff --git a/string/trie.md b/string/trie.md index 5987f4d..2750dde 100644 --- a/string/trie.md +++ b/string/trie.md @@ -2116,6 +2116,114 @@ public: * * * +> [!NOTE] **[LeetCode 3093. 最长公共后缀查询](https://leetcode.cn/problems/longest-common-suffix-queries/)** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 显然是 trie 核心在于压缩空间 +> +> => 要想到 **考虑偏序性质 可以直接在插入时维护** + +
+详细代码 + + +##### **C++** + +```cpp +const static int N = 1e4 + 10, M = 5e5 + 10; +int t[N]; + +struct Node { + Node * child[26]; + int p; + Node() { + for (int i = 0; i < 26; ++ i ) + child[i] = nullptr; + p = -1; + } +} *root; + +Node * rs[M]; +int tot; +Node * prep(int idx) { + rs[idx] = new Node(); + return rs[idx]; +} + +class Solution { +public: + void init() { + root = new Node(); + tot = 0; + } + void compare(Node * p, int i) { + // ATTENTION: 考虑严格偏序性质 可以直接在插入的时候 O(1) 维护... 不需要集合or每次全量sort + // 进而压缩空间避免MLE + int a = p->p, b = i; + if (a == -1 || (ws[a].size() > ws[b].size() || ws[a].size() == ws[b].size() && a > b)) + p->p = b; + } + void insert(int i, string & w) { + auto p = root; + compare(p, i); + for (auto c : w) { + int u = c - 'a'; + if (!p->child[u]) + p->child[u] = prep(tot ++ ); + p = p->child[u]; + compare(p, i); // ATTENTION + } + } + int query(string & w) { + auto p = root; + for (auto c : w) { + int u = c - 'a'; + if (!p->child[u]) + break; + p = p->child[u]; + } + return p->p; + } + + vector ws; + + vector stringIndices(vector& wordsContainer, vector& wordsQuery) { + init(); + + this->ws = wordsContainer; + + for (int i = 0; i < ws.size(); ++ i ) { + auto & w = ws[i]; + reverse(w.begin(), w.end()); + insert(i, w); + } + + vector res; + for (auto & w : wordsQuery) { + reverse(w.begin(), w.end()); + res.push_back(query(w)); + } + return res; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * + ### 离线 trie > [!NOTE] **[LeetCode 1707. 与数组中元素的最大异或值](https://leetcode.cn/problems/maximum-xor-with-an-element-from-array/)** diff --git a/topic/prepare.md b/topic/prepare.md index f7e9350..0544333 100644 --- a/topic/prepare.md +++ b/topic/prepare.md @@ -234,3 +234,144 @@ public:
* * * + +> [!NOTE] **[LeetCode 3086. 拾起 K 个 1 需要的最少行动次数](https://leetcode.cn/problems/minimum-moves-to-pick-k-ones/)** +> +> 题意: TODO + +> [!TIP] **思路** +> +> 思路完全正确 需要增强对复杂度的敏感度 理清楚写就好了 + +
+详细代码 + + +##### **C++** + +```cpp +class Solution { +public: + // 题目数据范围提示指明必然有解 + // + // 考虑在某个具体的下标: + // 1. 无成本获取当前下标的 1 + // 2. 1成本获取隔壁的 1 + // 3. 2成本在旁边造1并交换取得 + // 4. 剩下只能从左右两侧慢慢挪过来 成本与距离有关 + // => 如何处理第四种情况? + // 能够想到 二分左右两侧距离 在特定距离内找到满足特定数量的1 => 【理清楚 复杂度是可以接受的】 + + using LL = long long; + using PII = pair; + const static int N = 1e5 + 10; + + int n; + int cnt[N]; + LL sum[N]; + + // ATTENTION 注意边界计算细节 + PII get_l(int i, int mid) { + // [l, r] + // l: (i - mid) - 1 + return {max(0, i - mid - 1), max(0, i - 2)}; + } + PII get_r(int i, int mid) { + // [l, r] + // l: (i + 2) - 1 + return {min(n, i + 2 - 1), min(n, i + mid)}; + } + + int calc(int i, int mid) { + auto [l1, r1] = get_l(i, mid); + auto [l2, r2] = get_r(i, mid); + + int c1 = cnt[r1] - cnt[l1]; + int c2 = cnt[r2] - cnt[l2]; + + return c1 + c2; + } + + long long minimumMoves(vector& nums, int k, int maxChanges) { + this->n = nums.size(); + + // 预处理 + { + memset(cnt, 0, sizeof cnt); + memset(sum, 0, sizeof sum); + for (int i = 1; i <= n; ++ i ) { + cnt[i] = cnt[i - 1] + nums[i - 1]; + sum[i] = sum[i - 1] + 1ll * i * nums[i - 1]; // ATTENTION + } + } + + LL res = 1e16; + for (int i = 1; i <= n; ++ i ) { // 枚举每一个位置 + LL t = 0; + int need = k - nums[i - 1]; // 0 成本消耗当前位置 + + for (auto x : {i - 1, i + 1}) // 1 成本消耗左右位置 + if (need > 0 && x >= 1 && x <= n && nums[x - 1] == 1) { + need -- ; + t ++ ; + } + + // 2 成本在旁边位置制造1并消耗 + { + int cost = min(need, maxChanges); + need -= cost, t += cost * 2; + } + + if (need > 0) { // 仍然不够 则需要从左右两侧找1挨个挪过来 消耗与距离有关 + int l = 2, r = max(i - 1, n - i); // 左右扩展的距离, 其中 mid 的距离【可以】取到 + + while (l < r) { + int mid = l + (r - l) / 2; + + if (calc(i, mid) < need) + l = mid + 1; + else + r = mid; + } + + if (!calc(i, l)) + continue; + + auto [l1, r1] = get_l(i, l); + auto [l2, r2] = get_r(i, l); + + int c1 = cnt[r1] - cnt[l1]; + int c2 = cnt[r2] - cnt[l2]; + + LL t1 = 1ll * c1 * i - (sum[r1] - sum[l1]); // 左侧全部拿下的开销 依赖前缀和预处理 => ATTENTION 预处理【可行】的敏感度 想到了但没实施 + LL t2 = (sum[r2] - sum[l2]) - 1ll * c2 * i; + + t += t1 + t2; + + // TODO: revisit + // 有没有可能 这时候 c1+c2 > need ? 多了一个 + if (c1 + c2 > need) { // ATTENTION 理论上此时 c1+c2 = need + 1 (思考) + t -= l; + // 虽然不写能过 但是是有逻辑问题的 这里必须要追加判断 + } + } + + res = min(res, t); + } + return res; + } +}; +``` + +##### **Python** + +```python + +``` + + +
+ +
+ +* * * \ No newline at end of file