算法分析:
环形问题,先考虑线性。这种一维上的dp,我们一般设状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示规划了前
i
i
i个阶段,睡了
j
j
j个小时,且第i个小时是睡着的,恢复的最大体力值。则很容易写出如下方程:
f
[
i
]
[
j
]
=
m
a
x
{
f
[
j
]
[
s
]
+
d
}
f[i][j] = max\{f[j][s] + d\}
f[i][j]=max{f[j][s]+d}
j是枚举量,s是枚举量,d是[s+1,i]这段的情况,有可能前半段睡着,后面醒了,或者前面醒了,后面又睡着,等复杂情况。这个状态不太可行。应该增加维度。
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示规划了前 i i i个阶段,睡了 j j j个小时,第i个小时的状态是k,恢复的最大体力值。其中k为1表示是睡着的,k为0表示是清醒的。这样状态就好转移多了。
1.假设休息时间不会跨越n和1,就是线性上的问题。
f
[
i
]
[
j
]
[
0
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
[
0
]
,
f
[
i
−
1
]
[
j
]
[
1
]
)
f
[
i
]
[
j
]
[
1
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
1
]
[
0
]
,
f
[
i
−
1
]
[
j
−
1
]
[
1
]
+
u
[
i
]
)
\begin{aligned} f[i][j][0] & = max(f[i-1][j][0], f[i-1][j][1]) \\ f[i][j][1] & = max(f[i-1][j-1][0], f[i-1][j-1][1] + u[i]) \end{aligned}
f[i][j][0]f[i][j][1]=max(f[i−1][j][0],f[i−1][j][1])=max(f[i−1][j−1][0],f[i−1][j−1][1]+u[i])
边界:
f
[
1
]
[
1
]
[
0
]
=
0
;
f
[
1
]
[
1
]
[
1
]
=
0
;
f[1][1][0] = 0; f[1][1][1] = 0;
f[1][1][0]=0;f[1][1][1]=0;,其余为负无穷。
2.以上没有考虑n和1跨越问题,即在第n小时是休息的,这样第1小时能够恢复体力值。只要强制定义为第1小时能恢复体力值,最后求 f [ n ] [ b ] [ 1 ] f[n][b][1] f[n][b][1]即可。其余一样。
边界:
f
[
1
]
[
1
]
[
1
]
=
u
[
1
]
f[1][1][1] = u[1]
f[1][1][1]=u[1],其余为负无穷。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long int
int n, b, u[3835];
ll f[2][3805][2];
int main() // 不滚动数组会MLE
{
scanf("%d%d", &n, &b);
for (int i = 1; i <= n; ++i) scanf("%d", &u[i]);
memset(f, 0xcf, sizeof(f));
f[1&1][0][0] = 0; f[1&1][1][1] = 0;
for (int i = 2; i <= n; ++i)
for (int j = 0; j <= b; ++j)
{
f[i&1][j][0] = max(f[(i-1)&1][j][0], f[(i-1)&1][j][1]);
if (j >= 1) f[i&1][j][1] = max(f[(i-1)&1][j-1][0], f[(i-1)&1][j-1][1] + u[i]);
}
ll ans = 0;
ans = max(f[n&1][b][0], f[n&1][b][1]);
//
memset(f, 0xcf, sizeof(f));
f[1&1][1][1] = u[1];
for (int i = 2; i <= n; ++i)
for (int j = 0; j <= b; ++j)
{
f[i&1][j][0] = max(f[(i-1)&1][j][0], f[(i-1)&1][j][1]);
if (j >= 1) f[i&1][j][1] = max(f[(i-1)&1][j-1][0], f[(i-1)&1][j-1][1] + u[i]);
}
ans = max(ans, f[n&1][b][1]);
printf("%lld\n", ans);
return 0;
}
反思与总结:
1.空间超了,要滚动。
2.环形问题除了常用的“破环成链”外,这种“线性+特殊情况”的模式也可以使用:两次dp。第一次线性做dp,第二次通过适当赋值和假设条件,保证计算出的状态等价于把断开的位置强制相连。
3.数据要用long long。