思路
从标签从题面不难看出,这道题需要使用 dp。
所以我们就按照 “dp 三部曲”来求解。
1.定义状态
首先,最容易想到的当然是设 d p i dp_i dpi 为前 i i i 轮的最大得分,但是我们会发现,光用一维的 d p i dp_i dpi 来表示,是远远不够的。
然后,我们考虑二维 dp。因为跟得分有关的还有出的牌。新的状态无非就是
d
p
i
,
j
(
j
∈
{
0
,
1
,
2
}
)
dp_{i,j}(j\in \{0,1,2\})
dpi,j(j∈{0,1,2}) 表示前
i
i
i 轮,在第
i
i
i 轮出
j
j
j 号牌时的最大得分嘛!如果这个你能理解,那么非常棒,你已经离第一步的成功不远了!
当你兴致勃勃的用这个思路写代码时,你会发现还是不够。我们只好使用三维 dp。因为换牌会导致扣分,所以状态应改为: d p i , j , k dp_{i,j,k} dpi,j,k 表示前 i i i 轮,在第 i i i 轮出 j j j 号牌,此时已经换了 k k k 次牌时的最大得分。
很好!那么我们的状态就定义好了!
2.赋初值
在这道题中,没有进行计算时(即第 0 0 0 轮), d p 0 , j , k dp_{0,j,k} dp0,j,k 永远为 0 0 0,而全局数组初始值本就为 0 0 0,因此我们无须手动赋初值。
3.状态转移方程
最最最复杂的部分来了。在讲状态转移方程之前,我要讲一讲我的思路。
如果让你找除了
j
j
j 牌之外的两张牌,你会怎么找?
这里我来讲一讲我的方法:首先,牌只有
0
,
1
,
2
0,1,2
0,1,2 三种,这时我就想到了取模。没错,在这道题中,取模非常有用。就拿刚才的问题来说,剩下的两张牌就可以用
(
j
+
1
)
m
o
d
3
(j+1)\bmod 3
(j+1)mod3 和
(
j
+
2
)
m
o
d
3
(j+2)\bmod 3
(j+2)mod3 来计算。
除了找另外的牌,取余还可以用来判断胜负。这里我写了一个 pk \operatorname{pk} pk 函数,大家自己品味。
int pk(int x,int y){
if(x == y) return 1; //平局
if((x+1)%3 == y) return 0; //x输
return 2; //x胜
}
有了这个基础,我们就可以推出比其他人简单的状态转移方程了。
很简单,状态转移方程如下:
d
p
i
,
j
,
k
=
max
{
d
p
i
−
1
,
j
,
k
+
pk
(
j
,
c
i
)
×
a
i
d
p
i
−
1
,
(
j
+
1
)
m
o
d
3
,
k
+
pk
(
j
,
c
i
)
×
a
i
−
b
k
d
p
i
−
1
,
(
j
+
2
)
m
o
d
3
,
k
+
pk
(
j
,
c
i
)
×
a
i
−
b
k
dp_{i,j,k}=\max \begin{cases} dp_{i-1,j,k}+\operatorname{pk}(j,c_i)\times a_i \\ dp_{i-1,(j+1)\bmod 3,k}+\operatorname{pk}(j,c_i)\times a_i-b_k \\ dp_{i-1,(j+2)\bmod 3,k}+\operatorname{pk}(j,c_i)\times a_i-b_k \end{cases}
dpi,j,k=max⎩
⎨
⎧dpi−1,j,k+pk(j,ci)×aidpi−1,(j+1)mod3,k+pk(j,ci)×ai−bkdpi−1,(j+2)mod3,k+pk(j,ci)×ai−bk
这里
pk
\operatorname{pk}
pk 就是刚刚提到的函数。
有了这些,代码肯定就出来了。
代码
#include <bits/stdc++.h>
using namespace std;
int a[1010],b[1010],c[1010];
long long dp[1010][3][1010];//注意要开long long
int pk(int x,int y){
if(x == y) return 1; //平局
if((x+1)%3 == y) return 0; //x输
return 2; //x胜
}
int main(){
int n;
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i];
}
for(int i = 1;i < n;i++){
cin>>b[i];
b[i] += b[i-1];//提前执行前缀和
}
for(int i = 1;i <= n;i++){
cin>>c[i];
}
for(int i = 1;i <= n;i++){
for(int j = 0;j <= 2;j++){
for(int k = 0;k < i;k++){
dp[i][j][k] = dp[i-1][j][k]+pk(j,c[i])*a[i];
if(k > 0) dp[i][j][k] = max(dp[i][j][k],max(dp[i-1][(j+1)%3][k-1]+pk(j,c[i])*a[i],dp[i-1][(j+2)%3][k-1]+pk(j,c[i])*a[i]));//见提示1
}
}
}
long long ans = 0xc0c0c0c0c0c0c0c0;//见提示2
for(int j = 0;j <= 2;j++){
for(int k = 0;k < n;k++){
ans = max(ans,dp[n][j][k]-b[k]);//见提示3
}
}
cout<<ans;
return 0;
}
最后的提示
- 当 k = 0 k=0 k=0 时, k − 1 k-1 k−1 会导致数组越界,需要特判。
- 有可能换牌太多导致得分为负数,故初始化为无限小。
- 一定要在最后再减换牌带来的扣分。
不然就会像作者一样 70pts。