题目大意
给你一个有 n n n个元素的集合,第 i i i个元素有 a i a_i ai个,有两个操作:1.取走值从l到r的元素各一个。2.取走值为i的元素任意x个。问最少执行多少次操作把集合内所有元素取完。
解题思路
思路一:贪心 最坏
O
(
n
2
)
O(n^2)
O(n2),但实际上非常高效
可以发现在对某个区间执行操作1时,一定要反复执行直到该区间中的一个值的个数为0,这样的操作1才是有意义的。因此可以采用分治思想,对于一个区间
l
,
r
l, r
l,r,要么全用操作2将区间内所有数取走(一共
r
−
l
+
1
r - l + 1
r−l+1次操作2),要么对整个区间使用操作1,直到某个数个数变为0,并且把整个序列按0分段当作子问题递归处理。
思路二:DP
O
(
n
2
)
O(n^2)
O(n2)
通过分析可以发现操作2最好不要在相同元素上使用超过一次。这样设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示第
i
i
i种元素用操作1取出
j
j
j个所用的操作总个数。转移: (如果前面使用了k次操作1取出了
k
k
k个
i
−
1
i - 1
i−1)
- 当
k
<
j
k < j
k<j时,我们可以让j中的k个跟着前面的一起取走,而剩下的就是增加的代价,如果
j
<
a
[
i
]
j < a[i]
j<a[i]还要使用一次操作2将其变为j个
f [ i ] [ j ] = min { f [ i − 1 ] [ k ] + ( j − k ) + I ( j < a [ i ] ) } = min { f [ i − 1 ] [ k ] − k } + j + I ( j < a [ i ] ) f[i][j] = \min \{f[i-1][k] + (j - k) + \mathbb I (j < a[i]) \} = \min\{f[i-1][k] - k\}+j + \mathbb I (j < a[i]) f[i][j]=min{f[i−1][k]+(j−k)+I(j<a[i])}=min{f[i−1][k]−k}+j+I(j<a[i]) - 当 j ≤ k j \leq k j≤k时, 我们可以让所有的j个跟着前面的一起被取走不需要代价,如果 j < a [ i ] j < a[i] j<a[i]还要使用一次操作2将其变为j个 f [ i ] [ j ] = min { f [ i − 1 ] [ k ] + I ( j < a [ i ] ) } = min { f [ i − 1 ] [ k ] } + I ( j < a [ i ] ) f[i][j] = \min\{ f[i-1][k] +\mathbb I (j < a[i]) \} = \min \{f[i - 1][k]\} + \mathbb I (j < a[i]) f[i][j]=min{f[i−1][k]+I(j<a[i])}=min{f[i−1][k]}+I(j<a[i])
(如果理解不了,可以想一下只考虑用操作1不考虑用操作2,如何求操作次数)
再考虑优化转移,第一种情况中的 min { f [ i − 1 ] [ k ] − k } \min\{f[i-1][k] - k\} min{f[i−1][k]−k}可以通过维护前缀最小值来解决。第二种转移直接通过维护后缀最小值解决 min { f [ i − 1 ] [ k ] } \min \{f[i - 1][k]\} min{f[i−1][k]},这样转移就是 O ( 1 ) O(1) O(1)的了。
代码实现
具体细节请参考代码:
思路一:(50ms)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
int a[MAXN], n;
int solve(int l, int r) {
if (r < l) return 0;
int p = *min_element(a + l, a + r + 1);
int t = l, ans = p;
for (int i = l; i <= r; i++) {
a[i] -= p;
// 按 0 分段计算
if (!a[i]) ans += solve(t, i - 1), t = i + 1;
}
ans += solve(t, r);
return min(ans, r - l + 1);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
printf("%d\n", solve(1, n));
return 0;
}
思路二:(250ms)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
int f[MAXN][MAXN], mi[MAXN], a[MAXN], n;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
f[j][i] = 1e9;
mi[n + 1] = 1e9; f[0][0] = 0;
for (int i = 1; i <= n; i++) {
int c = 1e9;
for (int j = n; j >= 0; j--) mi[j] = min(f[i - 1][j], mi[j + 1]); // 维护后缀最小值
for (int j = 0; j <= min(a[i], n); j++) {
c = min(c, f[i - 1][j] - j); // 维护前缀最小值
f[i][j] = min(mi[j], c + j) + (j < a[i]);
}
}
int ans = 1e9;
for (int j = 0; j <= min(a[n], n); j++)
ans = min(ans, f[n][j]);
cout << ans << endl;
return 0;
}