From aab6c397c0aea5675178953a6f5eb7579c8a4ed5 Mon Sep 17 00:00:00 2001 From: duxiaohao Date: Fri, 23 Aug 2024 22:05:02 +0800 Subject: [PATCH] acwing blog --- ...\342\200\224\345\214\272\351\227\264DP.md" | 30 +-- ...\346\200\201\345\216\213\347\274\251DP.md" | 180 ++++++++-------- ...\342\200\224\347\272\277\346\200\247DP.md" | 198 ++++++++++-------- ...06\345\214\226\346\220\234\347\264\242.md" | 54 ++--- ...\344\275\215\347\273\237\350\256\241DP.md" | 46 ++-- ...\350\256\241\346\225\260\347\261\273DP.md" | 59 +++--- 6 files changed, 302 insertions(+), 265 deletions(-) diff --git "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\345\214\272\351\227\264DP.md" "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\345\214\272\351\227\264DP.md" index fb60515..7a9cef3 100644 --- "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\345\214\272\351\227\264DP.md" +++ "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\345\214\272\351\227\264DP.md" @@ -8,29 +8,33 @@ using namespace std; -const int N = 310; +const int N = 310; // 定义数组的最大长度 -int n; -int s[N]; -int f[N][N]; +int n; // 石子堆的数量 +int s[N]; // s[i]: 前i堆石子的总重量 +int f[N][N]; // f[i][j]: 将第i堆到第j堆石子合并成一堆的最小代价 int main() { - scanf("%d", &n); - for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]); + scanf("%d", &n); // 输入石子堆的数量n + for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]); // 读取每堆石子的重量 + + // 计算前缀和,s[i]现在表示前i堆石子的总重量 for (int i = 1; i <= n; i ++ ) s[i] += s[i - 1]; - for (int len = 2; len <= n; len ++ ) - for (int i = 1; i + len - 1 <= n; i ++ ) + // 动态规划求解 + for (int len = 2; len <= n; len ++ ) // 遍历石子堆的长度 + for (int i = 1; i + len - 1 <= n; i ++ ) // 遍历起始位置 { - int l = i, r = i + len - 1; - f[l][r] = 1e8; - for (int k = l; k < r; k ++ ) - f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); + int l = i, r = i + len - 1; // 当前石子堆的起始和结束位置 + f[l][r] = 1e8; // 初始化为一个较大的值 + for (int k = l; k < r; k ++ ) // 遍历分割点 + f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); // 更新最优解 } - printf("%d\n", f[1][n]); + printf("%d\n", f[1][n]); // 输出将所有石子堆合并成一堆的最小代价 + return 0; } ``` \ No newline at end of file diff --git "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\212\266\346\200\201\345\216\213\347\274\251DP.md" "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\212\266\346\200\201\345\216\213\347\274\251DP.md" index 6ef72e3..a98bca8 100644 --- "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\212\266\346\200\201\345\216\213\347\274\251DP.md" +++ "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\212\266\346\200\201\345\216\213\347\274\251DP.md" @@ -3,45 +3,52 @@ ### AcWing 291. 蒙德里安的梦想 ```c++ -#include -#include -#include +#include // 用于memset函数 +#include // 输入输出流 +#include // 标准算法库,这里未直接使用 using namespace std; -const int N = 12, M = 1 << N; +const int N = 12; // 定义行的最大数量 +const int M = 1 << N; // 定义列的状态表示的最大数量(2^N) -int n, m; -long long f[N][M]; -bool st[M]; +int n, m; // n 行数, m 矩形数量 +long long f[N][M]; // 动态规划数组,f[i][j]表示放置i个矩形后列状态为j的方案数 +bool st[M]; // 判断两种状态是否可以相邻的数组 int main() { - while (cin >> n >> m, n || m) + while (cin >> n >> m, n || m) // 循环读入数据直到n和m均为0 { - for (int i = 0; i < 1 << n; i ++ ) + // 预处理st数组,判断两种状态是否可以相邻 + for (int i = 0; i < 1 << n; i++) { int cnt = 0; - st[i] = true; - for (int j = 0; j < n; j ++ ) - if (i >> j & 1) + st[i] = true; // 默认为true + for (int j = 0; j < n; j++) + if (i >> j & 1) // 检查第j位是否为1 { - if (cnt & 1) st[i] = false; - cnt = 0; + if (cnt & 1) // 如果之前有奇数个连续0,则这两种状态不可以相邻 + st[i] = false; + cnt = 0; // 重置计数器 } - else cnt ++ ; - if (cnt & 1) st[i] = false; + else + cnt++; // 计数连续的0 + if (cnt & 1) // 检查最后是否有奇数个连续0 + st[i] = false; } - memset(f, 0, sizeof f); - f[0][0] = 1; - for (int i = 1; i <= m; i ++ ) - for (int j = 0; j < 1 << n; j ++ ) - for (int k = 0; k < 1 << n; k ++ ) - if ((j & k) == 0 && st[j | k]) - f[i][j] += f[i - 1][k]; - - cout << f[m][0] << endl; + memset(f, 0, sizeof f); // 初始化动态规划数组 + f[0][0] = 1; // 初始状态,没有放置任何矩形 + + // 动态规划转移 + for (int i = 1; i <= m; i++) // 枚举矩形数量 + for (int j = 0; j < 1 << n; j++) // 枚举当前列的状态 + for (int k = 0; k < 1 << n; k++) // 枚举前一列的状态 + if ((j & k) == 0 && st[j | k]) // 如果两个状态没有重叠且可以相邻 + f[i][j] += f[i - 1][k]; // 更新动态规划数组 + + cout << f[m][0] << endl; // 输出结果,即放置m个矩形后所有列状态为0的方案数 } return 0; } @@ -49,61 +56,68 @@ int main() 去除无效状态的优化写法,230ms ```c++ -#include -#include -#include -#include +#include // 用于memset函数 +#include // 输入输出流 +#include // 标准算法库,这里未直接使用 +#include // 使用vector容器 using namespace std; -typedef long long LL; +typedef long long LL; // 定义长整型别名 -const int N = 12, M = 1 << N; +const int N = 12; // 定义行的最大数量 +const int M = 1 << N; // 定义列的状态表示的最大数量(2^N) -int n, m; -LL f[N][M]; -vector state[M]; -bool st[M]; +int n, m; // n 行数, m 矩形数量 +LL f[N][M]; // 动态规划数组,f[i][j]表示放置i个矩形后列状态为j的方案数 +vector state[M]; // 存储每个状态可以转移到的状态 +bool st[M]; // 判断两种状态是否可以相邻的数组 int main() { - while (cin >> n >> m, n || m) + while (cin >> n >> m, n || m) // 循环读入数据直到n和m均为0 { - for (int i = 0; i < 1 << n; i ++ ) + // 预处理st数组,判断两种状态是否可以相邻 + for (int i = 0; i < 1 << n; i++) { int cnt = 0; - bool is_valid = true; - for (int j = 0; j < n; j ++ ) - if (i >> j & 1) + bool is_valid = true; // 初始设为true + for (int j = 0; j < n; j++) + if (i >> j & 1) // 检查第j位是否为1 { - if (cnt & 1) + if (cnt & 1) // 如果之前有奇数个连续0,则这两种状态不可以相邻 { is_valid = false; - break; + break; // 一旦发现不合法就退出循环 } - cnt = 0; + cnt = 0; // 重置计数器 } - else cnt ++ ; - if (cnt & 1) is_valid = false; - st[i] = is_valid; + else + cnt++; // 计数连续的0 + if (cnt & 1) // 检查最后是否有奇数个连续0 + is_valid = false; + st[i] = is_valid; // 标记状态是否有效 } - for (int i = 0; i < 1 << n; i ++ ) + // 预处理state数组,对于每种状态找到所有可以相邻的状态 + for (int i = 0; i < 1 << n; i++) { - state[i].clear(); - for (int j = 0; j < 1 << n; j ++ ) - if ((i & j) == 0 && st[i | j]) - state[i].push_back(j); + state[i].clear(); // 清空之前的记录 + for (int j = 0; j < 1 << n; j++) + if ((i & j) == 0 && st[i | j]) // 如果两个状态没有重叠且可以相邻 + state[i].push_back(j); // 添加到可以转移的状态列表中 } - memset(f, 0, sizeof f); - f[0][0] = 1; - for (int i = 1; i <= m; i ++ ) - for (int j = 0; j < 1 << n; j ++ ) - for (auto k : state[j]) - f[i][j] += f[i - 1][k]; - - cout << f[m][0] << endl; + memset(f, 0, sizeof f); // 初始化动态规划数组 + f[0][0] = 1; // 初始状态,没有放置任何矩形 + + // 动态规划转移 + for (int i = 1; i <= m; i++) // 枚举矩形数量 + for (int j = 0; j < 1 << n; j++) // 枚举当前列的状态 + for (auto k : state[j]) // 枚举可以从上一列转移过来的状态 + f[i][j] += f[i - 1][k]; // 更新动态规划数组 + + cout << f[m][0] << endl; // 输出结果,即放置m个矩形后所有列状态为0的方案数 } return 0; @@ -113,36 +127,38 @@ int main() ### AcWing 91. 最短Hamilton路径 ```c++ -#include -#include -#include +#include // 用于memset函数 +#include // 输入输出流 +#include // 标准算法库,这里使用min函数 using namespace std; -const int N = 20, M = 1 << N; +const int N = 20; // 定义城市数量的最大值 +const int M = 1 << N; // 定义状态表示的最大数量(2^N) -int n; -int w[N][N]; -int f[M][N]; +int n; // 城市数量 +int w[N][N]; // 二维数组,w[i][j]表示从城市i到城市j的距离 +int f[M][N]; // 动态规划数组,f[i][j]表示经过的城市集合为i且最后一个访问的城市为j的最短路径长度 int main() { - cin >> n; - for (int i = 0; i < n; i ++ ) - for (int j = 0; j < n; j ++ ) - cin >> w[i][j]; - - memset(f, 0x3f, sizeof f); - f[1][0] = 0; - - for (int i = 0; i < 1 << n; i ++ ) - for (int j = 0; j < n; j ++ ) - if (i >> j & 1) - for (int k = 0; k < n; k ++ ) - if (i >> k & 1) - f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]); - - cout << f[(1 << n) - 1][n - 1]; + cin >> n; // 读入城市数量 + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + cin >> w[i][j]; // 读入距离矩阵 + + memset(f, 0x3f, sizeof f); // 初始化动态规划数组为无穷大 + f[1][0] = 0; // 初始状态,只访问了第一个城市(0号城市),距离为0 + + // 动态规划转移 + for (int i = 0; i < 1 << n; i++) // 枚举所有可能的城市集合 + for (int j = 0; j < n; j++) // 枚举最后一个访问的城市 + if (i >> j & 1) // 如果城市j属于集合i + for (int k = 0; k < n; k++) // 枚举之前访问的城市 + if (i >> k & 1) // 如果城市k也属于集合i + f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]); // 动态规划转移 + + cout << f[(1 << n) - 1][n - 1] << endl; // 输出结果,即访问完所有城市并回到起点的最短路径长度 return 0; } diff --git "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\272\277\346\200\247DP.md" "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\272\277\346\200\247DP.md" index 8859a3b..f35e608 100644 --- "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\272\277\346\200\247DP.md" +++ "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\347\272\277\346\200\247DP.md" @@ -8,32 +8,37 @@ using namespace std; -const int N = 510, INF = 1e9; +const int N = 510; // 定义数组的最大长度 +const int INF = 1e9; // 定义无穷大值 -int n; -int a[N][N]; -int f[N][N]; +int n; // 三角形的层数 +int a[N][N]; // 三角形的数字 +int f[N][N]; // f[i][j]: 到达第i层第j个位置的最大路径和 int main() { - scanf("%d", &n); - for (int i = 1; i <= n; i ++ ) + scanf("%d", &n); // 输入三角形的层数n + + for (int i = 1; i <= n; i ++ ) // 读取每一层的数字 for (int j = 1; j <= i; j ++ ) scanf("%d", &a[i][j]); - for (int i = 0; i <= n; i ++ ) + for (int i = 0; i <= n; i ++ ) // 初始化f数组为负无穷 for (int j = 0; j <= i + 1; j ++ ) f[i][j] = -INF; - f[1][1] = a[1][1]; - for (int i = 2; i <= n; i ++ ) + f[1][1] = a[1][1]; // 从第一层的第一个位置开始 + + for (int i = 2; i <= n; i ++ ) // 从第二层开始遍历 for (int j = 1; j <= i; j ++ ) - f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]); + f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]); // 更新到达当前位置的最大路径和 + + int res = -INF; // 初始化结果为负无穷 + for (int i = 1; i <= n; i ++ ) // 找到最后一层的最大路径和 + res = max(res, f[n][i]); - int res = -INF; - for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]); + printf("%d\n", res); // 输出最大路径和 - printf("%d\n", res); return 0; } ``` @@ -46,28 +51,32 @@ int main() using namespace std; -const int N = 1010; +const int N = 1010; // 定义数组的最大长度 -int n; -int a[N], f[N]; +int n; // 序列的长度 +int a[N]; // 序列中的整数 +int f[N]; // f[i]: 以a[i]结尾的最长递增子序列的长度 int main() { - scanf("%d", &n); - for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); + scanf("%d", &n); // 输入序列的长度n - for (int i = 1; i <= n; i ++ ) + for (int i = 1; i <= n; i ++ ) // 读取序列中的每个元素 + scanf("%d", &a[i]); + + for (int i = 1; i <= n; i ++ ) // 遍历每个元素 { - f[i] = 1; // 只有a[i]一个数 - for (int j = 1; j < i; j ++ ) - if (a[j] < a[i]) - f[i] = max(f[i], f[j] + 1); + f[i] = 1; // 最短的递增子序列只包含a[i]本身 + for (int j = 1; j < i; j ++ ) // 遍历a[i]之前的元素 + if (a[j] < a[i]) // 如果前面的元素小于当前元素,则可能形成递增子序列 + f[i] = max(f[i], f[j] + 1); // 更新以a[i]结尾的最长递增子序列的长度 } - int res = 0; - for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); + int res = 0; // 初始化结果为0 + for (int i = 1; i <= n; i ++ ) // 找到最长递增子序列的长度 + res = max(res, f[i]); - printf("%d\n", res); + printf("%d\n", res); // 输出最长递增子序列的长度 return 0; } @@ -81,32 +90,34 @@ int main() using namespace std; -const int N = 100010; +const int N = 100010; // 定义数组的最大长度 -int n; -int a[N]; -int q[N]; +int n; // 序列的长度 +int a[N]; // 序列中的整数 +int q[N]; // 存储最长递增子序列中的最小结尾值 int main() { - scanf("%d", &n); - for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); + scanf("%d", &n); // 输入序列的长度n + + for (int i = 0; i < n; i ++ ) // 读取序列中的每个元素 + scanf("%d", &a[i]); - int len = 0; - for (int i = 0; i < n; i ++ ) + int len = 0; // 最长递增子序列的长度 + for (int i = 0; i < n; i ++ ) // 遍历每个元素 { - int l = 0, r = len; - while (l < r) + int l = 0, r = len; // 初始化二分查找的左右边界 + while (l < r) // 使用二分查找来定位a[i]应该插入的位置 { - int mid = l + r + 1 >> 1; - if (q[mid] < a[i]) l = mid; - else r = mid - 1; + int mid = l + r + 1 >> 1; // 计算中间位置 + if (q[mid] < a[i]) l = mid; // 如果中间位置的值小于a[i],则在右半部分查找 + else r = mid - 1; // 否则,在左半部分查找 } - len = max(len, r + 1); - q[r + 1] = a[i]; + len = max(len, r + 1); // 更新最长递增子序列的长度 + q[r + 1] = a[i]; // 更新q数组中的值 } - printf("%d\n", len); + printf("%d\n", len); // 输出最长递增子序列的长度 return 0; } @@ -120,25 +131,27 @@ int main() using namespace std; -const int N = 1010; +const int N = 1010; // 定义数组的最大长度 -int n, m; -char a[N], b[N]; -int f[N][N]; +int n, m; // 两个字符串的长度 +char a[N], b[N]; // 两个字符串 +int f[N][N]; // f[i][j]: 字符串a的前i个字符和字符串b的前j个字符的最长公共子序列的长度 int main() { - scanf("%d%d", &n, &m); - scanf("%s%s", a + 1, b + 1); + scanf("%d%d", &n, &m); // 输入两个字符串的长度n和m + + scanf("%s%s", a + 1, b + 1); // 读取两个字符串 - for (int i = 1; i <= n; i ++ ) - for (int j = 1; j <= m; j ++ ) + for (int i = 1; i <= n; i ++ ) // 遍历字符串a的每个字符 + for (int j = 1; j <= m; j ++ ) // 遍历字符串b的每个字符 { - f[i][j] = max(f[i - 1][j], f[i][j - 1]); - if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1); + f[i][j] = max(f[i - 1][j], f[i][j - 1]); // 如果当前字符不匹配,则取两者中的最大值 + if (a[i] == b[j]) // 如果当前字符匹配,则可能形成公共子序列 + f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1); // 更新最长公共子序列的长度 } - printf("%d\n", f[n][m]); + printf("%d\n", f[n][m]); // 输出最长公共子序列的长度 return 0; } @@ -152,29 +165,31 @@ int main() using namespace std; -const int N = 1010; +const int N = 1010; // 定义数组的最大长度 -int n, m; -char a[N], b[N]; -int f[N][N]; +int n, m; // 两个字符串的长度 +char a[N], b[N]; // 两个字符串 +int f[N][N]; // f[i][j]: 将字符串a的前i个字符转换为字符串b的前j个字符所需的最少操作次数 int main() { - scanf("%d%s", &n, a + 1); - scanf("%d%s", &m, b + 1); + scanf("%d%s", &n, a + 1); // 输入第一个字符串的长度n和字符串a + scanf("%d%s", &m, b + 1); // 输入第二个字符串的长度m和字符串b - for (int i = 0; i <= m; i ++ ) f[0][i] = i; - for (int i = 0; i <= n; i ++ ) f[i][0] = i; + for (int i = 0; i <= m; i ++ ) f[0][i] = i; // 初始化第一行 + for (int i = 0; i <= n; i ++ ) f[i][0] = i; // 初始化第一列 - for (int i = 1; i <= n; i ++ ) - for (int j = 1; j <= m; j ++ ) + for (int i = 1; i <= n; i ++ ) // 遍历字符串a的每个字符 + for (int j = 1; j <= m; j ++ ) // 遍历字符串b的每个字符 { - f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); - if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]); - else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1); + f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); // 删除或插入操作 + if (a[i] == b[j]) // 如果当前字符相同,则不需要额外的操作 + f[i][j] = min(f[i][j], f[i - 1][j - 1]); // 不需要替换 + else + f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1); // 替换操作 } - printf("%d\n", f[n][m]); + printf("%d\n", f[n][m]); // 输出将字符串a转换为字符串b所需的最少操作次数 return 0; } @@ -185,50 +200,53 @@ int main() ```c++ #include #include -#include +#include // 用于字符串操作 using namespace std; -const int N = 15, M = 1010; +const int N = 15; // 定义字符串的最大长度 +const int M = 1010; // 定义字符串的数量 -int n, m; -int f[N][N]; -char str[M][N]; +int n, m; // 字符串数量和查询次数 +int f[N][N]; // 用于计算编辑距离的动态规划表 +char str[M][N]; // 存储给定的字符串 +// 计算两个字符串之间的编辑距离 int edit_distance(char a[], char b[]) { - int la = strlen(a + 1), lb = strlen(b + 1); + int la = strlen(a); // 获取字符串a的长度 + int lb = strlen(b); // 获取字符串b的长度 - for (int i = 0; i <= lb; i ++ ) f[0][i] = i; - for (int i = 0; i <= la; i ++ ) f[i][0] = i; + for (int i = 0; i <= lb; i ++ ) f[0][i] = i; // 初始化第一行 + for (int i = 0; i <= la; i ++ ) f[i][0] = i; // 初始化第一列 - for (int i = 1; i <= la; i ++ ) - for (int j = 1; j <= lb; j ++ ) + for (int i = 1; i <= la; i ++ ) // 遍历字符串a的每个字符 + for (int j = 1; j <= lb; j ++ ) // 遍历字符串b的每个字符 { - f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); - f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j])); + f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); // 删除或插入操作 + f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j])); // 替换操作 } - return f[la][lb]; + return f[la][lb]; // 返回编辑距离 } int main() { - scanf("%d%d", &n, &m); - for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1); + scanf("%d%d", &n, &m); // 输入字符串数量n和查询次数m + for (int i = 0; i < n; i ++ ) scanf("%s", str[i]); // 读取每个字符串 while (m -- ) { - char s[N]; - int limit; - scanf("%s%d", s + 1, &limit); + char s[N]; // 目标字符串 + int limit; // 编辑距离的限制值 + scanf("%s%d", s, &limit); // 读取查询的目标字符串和限制值 - int res = 0; - for (int i = 0; i < n; i ++ ) - if (edit_distance(str[i], s) <= limit) - res ++ ; + int res = 0; // 初始化计数器 + for (int i = 0; i < n; i ++ ) // 对于每个给定的字符串 + if (edit_distance(str[i], s) <= limit) // 如果编辑距离小于等于限制值 + res ++ ; // 增加计数 - printf("%d\n", res); + printf("%d\n", res); // 输出满足条件的字符串数量 } return 0; diff --git "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\350\256\260\345\277\206\345\214\226\346\220\234\347\264\242.md" "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\350\256\260\345\277\206\345\214\226\346\220\234\347\264\242.md" index b3c41f5..12e3c4a 100644 --- "a/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\350\256\260\345\277\206\345\214\226\346\220\234\347\264\242.md" +++ "b/public/_blogs/AcWing/\345\212\250\346\200\201\350\247\204\345\210\222\342\200\224\342\200\224\350\256\260\345\277\206\345\214\226\346\220\234\347\264\242.md" @@ -3,51 +3,53 @@ ### AcWing 901. 滑雪 ```c++ -#include -#include -#include +#include // 用于memset函数 +#include // 输入输出流 +#include // 标准算法库,这里使用max函数 using namespace std; -const int N = 310; +const int N = 310; // 定义最大行数和列数 -int n, m; -int g[N][N]; -int f[N][N]; +int n, m; // 行数和列数 +int g[N][N]; // 二维数组,g[i][j]表示格子(i, j)的数值 +int f[N][N]; // 动态规划数组,f[i][j]表示从格子(i, j)出发能走过的最长路径长度 -int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; +int dx[4] = {-1, 0, 1, 0}; // 方向向量,表示上下左右四个方向 +int dy[4] = {0, 1, 0, -1}; // 方向向量,表示上下左右四个方向 +// 动态规划函数,计算从格子(x, y)出发能走过的最长路径长度 int dp(int x, int y) { - int &v = f[x][y]; - if (v != -1) return v; + int &v = f[x][y]; // 引用当前格子的最长路径长度 + if (v != -1) return v; // 如果已经计算过,则直接返回 - v = 1; - for (int i = 0; i < 4; i ++ ) + v = 1; // 最短路径至少包含当前格子 + for (int i = 0; i < 4; i++) // 枚举四个方向 { - int a = x + dx[i], b = y + dy[i]; - if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b]) - v = max(v, dp(a, b) + 1); + int a = x + dx[i], b = y + dy[i]; // 新的位置 + if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b]) // 如果新位置合法且数值递减 + v = max(v, dp(a, b) + 1); // 更新最长路径长度 } - return v; + return v; // 返回从当前格子出发的最长路径长度 } int main() { - scanf("%d%d", &n, &m); - for (int i = 1; i <= n; i ++ ) - for (int j = 1; j <= m; j ++ ) - scanf("%d", &g[i][j]); + scanf("%d%d", &n, &m); // 读入行数和列数 + for (int i = 1; i <= n; i++) + for (int j = 1; j <= m; j++) + scanf("%d", &g[i][j]); // 读入网格数值 - memset(f, -1, sizeof f); + memset(f, -1, sizeof f); // 初始化动态规划数组为-1 - int res = 0; - for (int i = 1; i <= n; i ++ ) - for (int j = 1; j <= m; j ++ ) - res = max(res, dp(i, j)); + int res = 0; // 记录全局最长路径长度 + for (int i = 1; i <= n; i++) // 枚举所有格子 + for (int j = 1; j <= m; j++) + res = max(res, dp(i, j)); // 更新全局最长路径长度 - printf("%d\n", res); + printf("%d\n", res); // 输出结果 return 0; } diff --git "a/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\346\225\260\344\275\215\347\273\237\350\256\241DP.md" "b/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\346\225\260\344\275\215\347\273\237\350\256\241DP.md" index 8ace2b2..5223466 100644 --- "a/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\346\225\260\344\275\215\347\273\237\350\256\241DP.md" +++ "b/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\346\225\260\344\275\215\347\273\237\350\256\241DP.md" @@ -11,17 +11,7 @@ using namespace std; const int N = 10; -/* - -001~abc-1, 999 - -abc - 1. num[i] < x, 0 - 2. num[i] == x, 0~efg - 3. num[i] > x, 0~999 - -*/ - +// 辅助函数:计算从num的第l位到第r位构成的数字 int get(vector num, int l, int r) { int res = 0; @@ -29,6 +19,7 @@ int get(vector num, int l, int r) return res; } +// 辅助函数:计算10的x次方 int power10(int x) { int res = 1; @@ -36,29 +27,30 @@ int power10(int x) return res; } +// 主函数:计算数字n中数字x出现的次数 int count(int n, int x) { - if (!n) return 0; + if (!n) return 0; // 如果n为0,则返回0 - vector num; + vector num; // 存储n的每一位数字 while (n) { - num.push_back(n % 10); - n /= 10; + num.push_back(n % 10); // 将n的每一位数字存入vector + n /= 10; // 移除最低位 } - n = num.size(); + n = num.size(); // n现在表示数字n的位数 - int res = 0; - for (int i = n - 1 - !x; i >= 0; i -- ) + int res = 0; // 初始化结果 + for (int i = n - 1 - !x; i >= 0; i -- ) // 从最高位到最低位遍历 { - if (i < n - 1) + if (i < n - 1) // 如果不是最高位 { - res += get(num, n - 1, i + 1) * power10(i); - if (!x) res -= power10(i); + res += get(num, n - 1, i + 1) * power10(i); // 加上前缀贡献 + if (!x) res -= power10(i); // 如果x为0,减去多余的贡献 } - if (num[i] == x) res += get(num, i - 1, 0) + 1; - else if (num[i] > x) res += power10(i); + if (num[i] == x) res += get(num, i - 1, 0) + 1; // 如果当前位等于x,加上贡献 + else if (num[i] > x) res += power10(i); // 如果当前位大于x,加上贡献 } return res; @@ -67,12 +59,12 @@ int count(int n, int x) int main() { int a, b; - while (cin >> a >> b , a) + while (cin >> a >> b && a) // 读取a和b,直到输入结束 { - if (a > b) swap(a, b); + if (a > b) swap(a, b); // 确保a ≤ b - for (int i = 0; i <= 9; i ++ ) - cout << count(b, i) - count(a - 1, i) << ' '; + for (int i = 0; i <= 9; i ++ ) // 对于每个数字0到9 + cout << count(b, i) - count(a - 1, i) << ' '; // 输出在[a, b]区间内该数字出现的次数 cout << endl; } diff --git "a/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\350\256\241\346\225\260\347\261\273DP.md" "b/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\350\256\241\346\225\260\347\261\273DP.md" index f506c4e..c0acc4e 100644 --- "a/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\350\256\241\346\225\260\347\261\273DP.md" +++ "b/public/_blogs/AcWing/\347\272\277\346\200\247\350\247\204\345\210\222\342\200\224\342\200\224\350\256\241\346\225\260\347\261\273DP.md" @@ -14,22 +14,24 @@ f\[i][j] = f\[i - 1][j] + f\[i][j - i]; using namespace std; -const int N = 1010, mod = 1e9 + 7; +const int N = 1010; // 定义数组的最大长度 +const int mod = 1e9 + 7; // 定义模数 -int n; -int f[N]; +int n; // 需要划分的整数 +int f[N]; // f[j]: 将j划分成若干个正整数之和的不同方法的数量 int main() { - cin >> n; - - f[0] = 1; - for (int i = 1; i <= n; i ++ ) - for (int j = i; j <= n; j ++ ) - f[j] = (f[j] + f[j - i]) % mod; - - cout << f[n] << endl; - + cin >> n; // 输入需要划分的整数n + + f[0] = 1; // 基础情况:0可以被唯一地划分成0 + + for (int i = 1; i <= n; i ++ ) // 遍历每个可能的划分数i + for (int j = i; j <= n; j ++ ) // 从i到n,计算将j划分成若干个正整数之和的方法数量 + f[j] = (f[j] + f[j - i]) % mod; // 更新f[j],将j划分成若干个正整数之和的方法数量 + + cout << f[n] << endl; // 输出将n划分成若干个正整数之和的不同方法的数量 + return 0; } ``` @@ -45,25 +47,28 @@ f\[i][j] = f\[i - 1][j - 1] + f\[i - j][j]; using namespace std; -const int N = 1010, mod = 1e9 + 7; +const int N = 1010; // 定义数组的最大长度 +const int mod = 1e9 + 7; // 定义模数 -int n; -int f[N][N]; +int n; // 需要划分的整数 +int f[N][N]; // f[i][j]: 将i划分成j个正整数之和的不同方法的数量 int main() { - cin >> n; - - f[1][1] = 1; - for (int i = 2; i <= n; i ++ ) - for (int j = 1; j <= i; j ++ ) - f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod; - - int res = 0; - for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod; - - cout << res << endl; - + cin >> n; // 输入需要划分的整数n + + f[1][1] = 1; // 基础情况:1只能被唯一地划分成1 + + for (int i = 2; i <= n; i ++ ) // 遍历每个需要划分的整数i + for (int j = 1; j <= i; j ++ ) // 从1到i,计算将i划分成j个正整数之和的方法数量 + f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod; // 更新f[i][j],将i划分成j个正整数之和的方法数量 + + int res = 0; // 初始化结果 + for (int i = 1; i <= n; i ++ ) // 计算将n划分成不同数量的正整数之和的方法数量之和 + res = (res + f[n][i]) % mod; // 累加不同划分方法的数量 + + cout << res << endl; // 输出将n划分成若干个正整数之和的不同方法的数量 + return 0; } ``` \ No newline at end of file