2022.10.13 总结
1.逐月 P8002
题意
有 \(n\) 个 好的 数 \(a_1, a_2, a_3 \dots a_n\)。
一个长度为 \(m\) 的序列是 好的 序列需要满足一下要求:
-
对于所有的 \(i\) \((1 \le i < m)\) ,都有 \(b_i < b_{i + 1}\)。
-
对于所有的 \(i\) \((1 \le i < m)\) ,都有 \(gcd(b_i, b_{i + 1} > 1)\)。 \(gcd(x, y)\) 表示 \(x\) 和 \(y\) 的最大公因数。
-
对于所有的 \(i\) \((1 \le i \le m)\) ,\(b_i\) 这个数是好的。
请你求出,最长的 好的 序列的长度是多少。
思路
30 分
满足第 \(3\) 条要求很简单,只要选的数都是序列 \(a\) 中的数就可以了。
而满足第 \(1\) 条要求也简单,将数组 \(a\) 排序即可。
那么这个问题就变成了:
找出序列 \(a\) 中的一个子序列,并且满足第 \(2\) 条要求。
那么就可以 搜索 了。
时间复杂度
每个元素有选与不选的两种选择,总共 \(n\) 个元素,\(O(2 ^ n)\)。
空间复杂度
原本的数组存储序列 \(a\),\(O(n)\)。
临时数组记录序列,\(O(n)\)。
空间复杂度 \(O(n)\)。
80 分
又是找子序列,又是要最长,所以最长上升子序列模型了解一下?(DP)
时间复杂度
枚举每个元素,\(O(n)\)。
枚举前面每个元素,寻找最大长度且满足要求,\(O(n)\)。
总时间复杂度为 \(O(n)\)。
空间复杂度
一维 \(dp\) 数组记录最长长度,\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int q, n, a[N], p[N];
int gcd(int a, int b){
if (!b) {
return a;
}
return gcd(b, a % b);
}
int main(){
cin >> q;
while (q--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
int c = -1;
for (int j = 1; j <= n; j++) {
p[j] = 1;
for (int k = 1; k < j; k++) {
if (gcd(a[j], a[k]) > 1) {
p[j] = max(p[j], p[k] + 1);
}
}
c = max(c, p[j]);
}
cout << c << endl;
for (int j = 1; j <= n; j++) { // 初始化
p[j] = 0;
}
}
return 0;
}
100 分
既然枚举 \(j\) 会超时,那我们就枚举一个不会超时的。
考虑枚举约数,因为既然是要使两个相邻元素之间的最大公约数 \(> 1\),那么它们一定不会互质,也就是有除了 \(1\) 以外的其他公共约数。
而约数总是成对出现的,其中一个 \(\le \sqrt{a_i}\),另一个 \(\ge \sqrt{a_i}\)。所以可以用 \(\sqrt{a_i}\) 的算法去枚举 \(a_i\) 的所有约数。
所以事情又变得简单了,用一个数组 \(g\) 记录最长长度, 也就是 \(g_i\) 表示末尾元素有 \(i\) 这个约数的序列的最长长度。
时间复杂度
枚举每个元素,\(O(n)\)。
枚举约数,\(O(\sqrt{a_i})\)。
总时间复杂度为 \(O(n \times \sqrt{a_i})\)。
空间复杂度
\(g\) 数组记录最长长度,\(O(a_i)\)。
代码
int F(){
for (int i = 1; i < N; i++) {
g[i] = 0;
}
int sum = 0;
for (int i = 1; i <= n; i++) {
int ans = 1;
for (int j = 2; j * j <= a[i]; j++) { // 枚举约数
if (a[i] % j == 0) {
ans = max(ans, max(g[j], g[a[i] / j]) + 1); // 更新最大值
}
}
sum = max(sum, ans);
for (int j = 1; j * j <= a[i]; j++) { // 更新最长长度
if (a[i] % j == 0) {
g[j] = max(g[j], ans);
g[a[i] / j] = max(g[a[i] / j], ans);
}
}
}
return sum;
}
2. 洛谷 P1510
题意
现在需要体积至少为 \(v\) 的石头,而还剩下 \(n\) 块石头,每块石头的体积为 \(a_i\),搬它的体力值为 \(b_i\)。
现在你有 \(c\) 的体力值,请问你能否得到体积为 \(v\) 的石头,如果可以,则输出还能剩下的最多体力值,如果不能,则输出 \(Impossible\)。
思路
100 分
这个题目很明显,每个石头只有选和不选两种选择,所以是 \(01\) 背包。
但是这个题目如果开 \(2\) 维数组做的话,空间就炸了,所以,我们需要一些别的方法去优化空间。
在你写 \(01\) 背包的时候,你会发现,第 \(i\) 种状态只和 \(i - 1\) 有关系,和第 \(i - 2 , i - 3 \dots i - i\) 都没有关系,所以,我们只需要记录 \(i\) 和 \(i - 1\),也就是说,只需要一个 \(2 \times c\) 的数组就可以完成了,这个就是 滚动数组。
时间复杂度
枚举状态和转移,\(O(n \times c)\)。
空间复杂度
滚动数组记录状态,\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, C = 10010;
int v, n, c, a[N], b[N], dp[5][C]; // 滚动数组
int main(){
cin >> v >> n >> c;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
}
int mo = c + 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= c; j++) {
dp[2][j] = dp[1][j];
if (j >= b[i]) {
dp[2][j] = max(dp[2][j], dp[1][j - b[i]] + a[i]);
}
if (dp[2][j] >= v) {
mo = min(mo, j);
}
}
for (int j = 0; j <= c; j++) {
dp[1][j] = dp[2][j];
}
}
if (mo <= c) {
cout << c - mo;
} else {
cout << "Impossible";
}
return 0;
}