diff --git a/Algorithm/DP-DynamicProgramming/Classification.md b/Algorithm/DP-DynamicProgramming/Classification.md new file mode 100644 index 0000000..85fa047 --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Classification.md @@ -0,0 +1,100 @@ +### 动态规划的分类 + +动态规划(Dynamic Programming, DP)是一种算法设计范式,用于解决具有重叠子问题和最优子结构的问题。它通过将问题分解成更小的子问题,并存储子问题的解来避免重复计算,从而提高效率。DP 的核心是状态定义、状态转移方程和边界条件。 + +DP 可以根据问题的结构和状态表示方式进行分类。常见的分类包括线性 DP、区间 DP、树形 DP、状态压缩 DP、数位 DP、背包 DP(有时视为线性 DP 的子类)、概率 DP 和博弈 DP 等。下面我将详细介绍每个分类,包括定义、特点、适用场景,并给出典型例子。对于每个例子,我会简要描述问题、状态定义和转移方程。如果需要代码实现,我可以进一步提供(这里用 Python 示例)。 + +#### 1. **线性 DP** + - **定义与特点**:问题可以沿着一个线性序列(如数组、字符串)进行处理,通常从左到右或从小到大递推。状态通常是一维或二维数组,转移简单,时间复杂度一般为 O(n) 或 O(n^2)。 + - **适用场景**:序列优化问题,如最长子序列、路径计数等。 + - **例子**: + - **斐波那契数列**:计算第 n 项斐波那契数(F(n) = F(n-1) + F(n-2))。 + - 状态:dp[i] 表示第 i 项的值。 + - 转移:dp[i] = dp[i-1] + dp[i-2]。 + - 边界:dp[0] = 0, dp[1] = 1。 + - 示例代码: + ```python + def fib(n): + if n <= 1: + return n + dp = [0] * (n + 1) + dp[1] = 1 + for i in range(2, n + 1): + dp[i] = dp[i-1] + dp[i-2] + return dp[n] + # 示例:fib(5) = 5 + ``` + - **最长递增子序列 (LIS)**:给定数组,求最长递增子序列的长度。 + - 状态:dp[i] 表示以第 i 个元素结尾的最长递增子序列长度。 + - 转移:dp[i] = max(dp[j] + 1) for j < i and nums[j] < nums[i]。 + - 边界:dp[i] = 1。 + +#### 2. **区间 DP** + - **定义与特点**:问题涉及合并或处理区间(如数组的子区间),通常枚举区间的长度和起点,从小区间向大区间递推。状态是二维数组 dp[l][r],表示区间 [l, r] 的最优值。 + - **适用场景**:区间合并、括号匹配等。 + - **例子**: + - **矩阵链乘法**:给定 n 个矩阵的维度,求最小乘法次数。 + - 状态:dp[i][j] 表示从第 i 到第 j 个矩阵的最小乘法次数。 + - 转移:dp[i][j] = min(dp[i][k] + dp[k+1][j] + dims[i-1]*dims[k]*dims[j]) for k in [i, j-1]。 + - 边界:dp[i][i] = 0。 + - 示例:矩阵尺寸 [10, 30, 5, 60],最小乘法次数为 7500。 + - **石子合并**:n 堆石子合并成一堆的最小成本(相邻堆合并成本为石子数和)。 + - 状态:dp[i][j] 表示合并 [i, j] 堆的最小成本。 + - 转移:类似矩阵链乘法。 + +#### 3. **树形 DP** + - **定义与特点**:在树结构上进行 DP,通常从叶子节点向上递推。状态定义在树节点上,可能包括子树的信息。常用 DFS 实现。 + - **适用场景**:树上的路径、覆盖、独立集等问题。 + - **例子**: + - **树上最大独立集**:在树中选节点,使无相邻节点被选,且节点权值和最大。 + - 状态:dp[u][0] 表示不选 u 的最大值;dp[u][1] 表示选 u 的最大值。 + - 转移:dp[u][0] = sum(max(dp[v][0], dp[v][1]) for v in children);dp[u][1] = weight[u] + sum(dp[v][0] for v in children)。 + - 边界:叶子节点 dp[u][0] = 0, dp[u][1] = weight[u]。 + - **树的最长路径**:求树中任意两节点的最长路径长度。 + +#### 4. **状态压缩 DP** + - **定义与特点**:当状态数量不多时,用二进制位表示集合状态(比特位 DP)。状态通常是 dp[mask] 或 dp[i][mask],mask 表示子集。 + - **适用场景**:组合优化,如子集问题,状态数不超过 2^20。 + - **例子**: + - **旅行商问题 (TSP)**:从起点访问所有城市并返回的最短路径。 + - 状态:dp[mask][u] 表示访问过 mask 集合的城市,最后在 u 的最小距离。 + - 转移:dp[mask][u] = min(dp[mask - {u}][v] + dist[v][u]) for v != u。 + - 边界:dp[1<= w[i]。 + - 优化:一维 dp[v] = max(dp[v], dp[v - w[i]] + val[i])(逆序)。 + - **完全背包**:物品无限选,转移正序。 + +#### 7. **概率 DP** + - **定义与特点**:涉及概率计算,状态表示期望值或概率。转移基于概率加权。 + - **适用场景**:随机过程、游戏期望。 + - **例子**: + - **扔硬币期望**:扔 n 次硬币,求达到 k 个正面的期望扔次数。 + - 状态:dp[i] 表示已有 i 个正面的期望剩余扔次数。 + - 转移:dp[i] = 1 + 0.5 * dp[i+1] + 0.5 * dp[i](需解方程)。 + +#### 8. **博弈 DP** + - **定义与特点**:两人博弈,状态表示当前局面下的最优值(胜负或分数)。常用 min-max。 + - **适用场景**:游戏、Nim 堆等。 + - **例子**: + - **Nim 游戏**:多堆石子,两人轮流取,求先手是否必胜。 + - 状态: Grundy 值(异或所有堆的 mex 值)。 + - **取石子游戏**:一堆石子,每次取 1~m 个,最后取者胜。 + +这些分类不是严格互斥的,有些问题可以归入多个类别(如背包可视为线性 DP)。在实际问题中,选择分类取决于问题结构。建议从简单线性 DP 开始练习,如 LeetCode 的 DP 专题。如果你有特定问题或需要代码实现某个例子,请提供更多细节! \ No newline at end of file diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/AT_dp_f LCS.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/AT_dp_f LCS.cpp new file mode 100644 index 0000000..ef0afb7 --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/AT_dp_f LCS.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +using namespace std; +int main(){ + string a, b; + cin >> a >> b; + int a_size = a.size(), b_size = b.size(); + vector>> dp(a.size() + 1, vector>(b_size + 1, {0, ""})); + for(int i = 1; i <= a_size; i++){ + for(int j = 1; j <= b_size; j++){ + if(a[i - 1] == b[j - 1]) { + dp[i][j].first = dp[i - 1][j - 1].first + 1; + string tmp = dp[i - 1][j - 1].second; + tmp.push_back(a[i - 1]); + dp[i][j].second = tmp; + } + else{ + dp[i][j].first = max(dp[i - 1][j].first, dp[i][j - 1].first); + if(dp[i - 1][j].first > dp[i][j - 1].first){ + dp[i][j].second = dp[i - 1][j].second; + } + else dp[i][j].second = dp[i][j - 1].second; + + } + } + } + //cout << dp[a_size][b_size].first << " " << dp[a_size][b_size].second << endl; + cout << dp[a_size][b_size].second << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/B3637 最长上升子序列.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/B3637 最长上升子序列.cpp new file mode 100644 index 0000000..ad2815a --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/B3637 最长上升子序列.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +using namespace std; +struct cmp{ + bool operator()(const pair& a, const pair& b){ + if(a.first != b.first) return a.first > b.first; + return a.second < b.second; + } +}; +int main(){ + int n; + cin >> n; + vector a(n); + for(int i = 0; i < n; i++){ + cin >> a[i]; + } + //dp[i]->ǰi pair(, ֵ) + vector dp(n, 1); + for(int i = 0; i < n; i++){ + for(int j = 0; j < i; j++){ + if(a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1); + } + } + int m = *max_element(dp.begin(), dp.end()); + cout << m << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa10003CuttingSticks.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa10003CuttingSticks.cpp new file mode 100644 index 0000000..6d0a52f --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa10003CuttingSticks.cpp @@ -0,0 +1,38 @@ +#include +#include +using namespace std; +int main(){ + int len, n; + cin >> len >> n; + vector a(n + 2); + vector> dp(n + 2, vector(n + 2, 0)); + //dp[i][j]Ϊиľ[i, j] 0|---1|-----2|----3|--4| ķ a[0] = 0 a[1] = x,,, a[n + 1] = len + a[0] = 0; a[n + 1] = len; + for(int i = 1; i <= n; i++){ + cin >> a[i]; + } + //a[i]Ƿֵ + /* + for(int i = 0; i <= n + 1; i++){ + for(int j = i; j <= n + 1 ;j++){ + dp[i][j] = 114514; + for(int k = i + 1; k < j; k++){ + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + a[j] - a[i]); + } + if(dp[i][j] == 114514) dp[i][j] = 0; + } + } */ + // DPȵ + for(int l = 2; l <= n + 1; l++){ // 䳤2 + for(int i = 0; i + l <= n + 1; i++){ + int j = i + l; + dp[i][j] = INT_MAX; + for(int k = i + 1; k < j; k++){ + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + a[j] - a[i]); + } + if(dp[i][j] == INT_MAX) dp[i][j] = 0; + } + } + cout << dp[0][n + 1] << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa11584划分回文串.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa11584划分回文串.cpp new file mode 100644 index 0000000..ba6a510 --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa11584划分回文串.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +int main(){ + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int T; + if(!(cin >> T)) return 0; + while(T--){ + string s; + cin >> s; + int n = s.size(); + if(n == 0){ + cout << 0 << "\n"; + continue; + } + // isPal[i][j] whether s[i..j] is palindrome + vector> isPal(n, vector(n, 0)); + // center expand to fill isPal (or use DP) + for(int center = 0; center < n; ++center){ + // odd length + int l = center, r = center; + while(l >= 0 && r < n && s[l] == s[r]){ + isPal[l][r] = 1; + --l; ++r; + } + // even length + l = center; r = center + 1; + while(l >= 0 && r < n && s[l] == s[r]){ + isPal[l][r] = 1; + --l; ++r; + } + } + + // dp[i] = min cuts for s[0..i] + const int INF = 1e9; + vector dp(n, INF); + for(int i = 0; i < n; ++i){ + if(isPal[0][i]){ + dp[i] = 0; + } else { + for(int j = 0; j < i; ++j){ + if(isPal[j+1][i]){ + dp[i] = min(dp[i], dp[j] + 1); + } + } + } + } + cout << dp[n-1] << "\n"; + } + return 0; +} + diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa1626括号序列.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa1626括号序列.cpp new file mode 100644 index 0000000..2f2a5d6 --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/Interval-DP/UVa1626括号序列.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +using namespace std; +const int INF = 1e9; +bool match(char a, char b){ + return (a == '(' && b == ')') || (a == '[' && b == ']'); +} +int main(){ + string s; + cin >> s; + int n = s.size(); + vector> dp(s.size() + 2, vector(s.size() + 2, 1)); + for(int l = 2; l <= n; l++){ + for(int i = 0; i < n - l + 1; i++){ + int j = i + l - 1; + dp[i][j] = INF; + for(int k = i; k < j; k++){ + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]); + } + if(match(s[i], s[j])) { + if (i + 1 <= j - 1) + dp[i][j] = min(dp[i][j], dp[i+1][j-1]); + else + dp[i][j] = 0; // ڵֱƥ + } + } + } + cout << dp[0][n - 1] << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/P1616 疯狂的采药.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P1616 疯狂的采药.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P1616 疯狂的采药.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P1616 疯狂的采药.cpp diff --git a/Algorithm/DP-DynamicProgramming/P2758 编辑距离.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P2758 编辑距离.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P2758 编辑距离.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P2758 编辑距离.cpp diff --git a/Algorithm/DP-DynamicProgramming/P2842 纸币问题 1.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P2842 纸币问题 1.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P2842 纸币问题 1.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/Knapsack-DP/P2842 纸币问题 1.cpp diff --git a/Algorithm/DP-DynamicProgramming/P1164 小A点菜.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/P1164 小A点菜.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P1164 小A点菜.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/P1164 小A点菜.cpp diff --git a/Algorithm/DP-DynamicProgramming/P1216 [IOI 1994 USACO1.5] 数字三角形 Number Triangles.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/P1216 [IOI 1994 USACO1.5] 数字三角形 Number Triangles.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P1216 [IOI 1994 USACO1.5] 数字三角形 Number Triangles.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/P1216 [IOI 1994 USACO1.5] 数字三角形 Number Triangles.cpp diff --git a/Algorithm/DP-DynamicProgramming/Linear-DP/P1439.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/P1439.cpp new file mode 100644 index 0000000..bb32733 --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Linear-DP/P1439.cpp @@ -0,0 +1,28 @@ +#include +#include +using namespace std; +int main(){ + int n; + cin >> n; + vector a(n), b(n); + for(int i = 0; i < n; i++){ + cin >> a[i]; + } + for(int i = 0; i < n; i++){ + cin >> b[i]; + } + int a_size = a.size(), b_size = b.size(); + vector> dp(a.size() + 1, vector(b_size + 1, 0)); + for(int i = 1; i <= a_size; i++){ + for(int j = 1; j <= b_size; j++){ + if(a[i - 1] == b[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } + else{ + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + cout << dp[a_size][b_size] << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/P1802 5 倍经验日.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/P1802 5 倍经验日.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/P1802 5 倍经验日.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/P1802 5 倍经验日.cpp diff --git a/Algorithm/DP-DynamicProgramming/UVA1347 旅行 Tour.cpp b/Algorithm/DP-DynamicProgramming/Linear-DP/UVA1347 旅行 Tour.cpp similarity index 100% rename from Algorithm/DP-DynamicProgramming/UVA1347 旅行 Tour.cpp rename to Algorithm/DP-DynamicProgramming/Linear-DP/UVA1347 旅行 Tour.cpp diff --git a/Algorithm/DP-DynamicProgramming/Tree-DP/树的最大独立集.cpp b/Algorithm/DP-DynamicProgramming/Tree-DP/树的最大独立集.cpp new file mode 100644 index 0000000..3998b2f --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Tree-DP/树的最大独立集.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +using namespace std; +void dfs(vector>& d, const vector>& adj_, vector& vs, int v){ + d[v][0] = 0; + d[v][1] = 1; + for(auto x : adj_[v]){ + if(vs[x] == false) { + vs[x] = true; + dfs(d, adj_, vs, x); + // + d[v][0] += max(d[x][0], d[x][1]); // ѡuѡ + d[v][1] += d[x][0]; // ѡu벻ѡ + } + + } +} + + +int main(){ + //ͼĻֱvectorڽӱ + int n; + cin >> n; + vector> adj(n); + vector> dp(n, vector(2, 0)); + vector visited(n, false); + //vector> choose(n, vector(n, 0)); + //ڵ0ʼn - 1 + for(int i = 0; i < n - 1; i++){ + int a, b; + cin >> a >> b; + adj[a].push_back(b); + adj[b].push_back(a); + } + /* + for(int l = 2; l <= n; l++){ + for(int i = 0; i < n - l + 1; i++){ + int j = i + l - 1; + for(int k = i; k < j; k++){ + if(adj[i][j]) + d[i][j] = max(dp[i][j], ) + } + } + }*/ + visited[0] = true; + dfs(dp, adj, visited, 0); // 0Ϊ + cout << max(dp[0][0], dp[0][1]) << endl; + return 0; +} diff --git a/Algorithm/DP-DynamicProgramming/Tree-DP/树的直径-最远点对.cpp b/Algorithm/DP-DynamicProgramming/Tree-DP/树的直径-最远点对.cpp new file mode 100644 index 0000000..ad6367c --- /dev/null +++ b/Algorithm/DP-DynamicProgramming/Tree-DP/树的直径-最远点对.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +using namespace std; +vector> adj; +vector dp; +void dfs(int v, int parent){ + //no return; + for(auto x : adj[v]){ + if(x == parent) continue; + dfs(x, v); + dp[v] = max(dp[v], dp[x] + 1); + } +} + +int main(){ + int n; + cin >> n; + adj.resize(n, vector()); + dp.resize(n, 1); + vector visited(n, false); + //ڵ0ʼn - 1 + for(int i = 0; i < n - 1; i++){ + int a, b; + cin >> a >> b; + adj[a].push_back(b); + adj[b].push_back(a); + } + dfs(0, -1); + int mmax = 0; + if(adj[0].size() == 1) cout << dp[0] << endl; + else{ + vector tmp; + for(auto x : adj[0]){ + tmp.push_back(dp[x]); + } + sort(tmp.begin(), tmp.end()); + mmax = *(tmp.end() - 1) + *(tmp.end() - 2); + cout << mmax + 1<< endl; + } + return 0; +} +/* +7 +0 1 +0 2 +1 3 +1 4 +2 5 +2 6 + +8 +0 1 +0 2 +1 3 +1 4 +2 5 +2 6 +3 7 +*/ diff --git a/Algorithm/IterativeDeepening/Egypt-Fraction-AI.exe b/Algorithm/IterativeDeepening/Egypt-Fraction-AI.exe deleted file mode 100644 index e7c9980..0000000 Binary files a/Algorithm/IterativeDeepening/Egypt-Fraction-AI.exe and /dev/null differ diff --git a/Algorithm/Recursion/P1044 [NOIP 2003 普及组] 栈.cpp b/Algorithm/Recursion/P1044 [NOIP 2003 普及组] 栈.cpp new file mode 100644 index 0000000..a2b6a4c --- /dev/null +++ b/Algorithm/Recursion/P1044 [NOIP 2003 普及组] 栈.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +using namespace std; +set> result; +void dfs(deque& path, deque& stack, deque& left){ + if(stack.size() == 0 && left.size() == 0){ + result.emplace(path); + return; + } + else if(stack.size() == 0){ + int tmp = left.front(); + stack.push_back(tmp); + left.pop_front(); + dfs(path, stack, left); + left.push_front(tmp); + stack.pop_back(); + } + else if(left.size() == 0){ + int tmp = stack.back(); + stack.pop_back(); + path.push_back(tmp); + dfs(path, stack, left); + stack.push_back(tmp); + path.pop_back(); + } + else { + { + int tmp = left.front(); + left.pop_front(); + stack.push_back(tmp); + dfs(path, stack, left); + stack.pop_back(); + left.push_front(tmp); + } + { + int tmp = stack.back(); + stack.pop_back(); + path.push_back(tmp); + dfs(path, stack, left); + path.pop_back(); + stack.push_back(tmp); + } +} + +} + +int main(){ + int n; + cin >> n; + deque l(n), s, p; + for(int i = 0; i < n; i++){ + l[i] = i + 1; + } + dfs(p, s, l); + cout << result.size() << endl; + /* + for(auto x : result){ + for(auto y : x){ + cout << y << " "; + } + cout << endl; + }*/ + for(auto x : *result.begin()){ + cout << x << " "; + } + cout << endl; + return 0; +} diff --git a/std-Cpp/regex/UVA10340 子序列 All in All.cpp b/std-Cpp/regex/UVA10340 子序列 All in All.cpp new file mode 100644 index 0000000..ec26538 --- /dev/null +++ b/std-Cpp/regex/UVA10340 子序列 All in All.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +using namespace std; +int main() { + std::string s, t; + while (std::cin >> s >> t) { + std::string pattern; + for (char c : s) { + pattern += std::regex_escape(std::string(1, c)) + ".*"; + } + // ȥ .* + if (!pattern.empty()) pattern = pattern.substr(0, pattern.size() - 2); + + std::regex re(pattern); + if (std::regex_match(t, re)) { + std::cout << "Yes\n"; + } else { + std::cout << "No\n"; + } + } + return 0; +} +