题解:P10111 [GESP202312 七级] 纸牌游戏

题目传送门


思路

从标签从题面不难看出,这道题需要使用 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 dpi1,j,k+pk(j,ci)×aidpi1,(j+1)mod3,k+pk(j,ci)×aibkdpi1,(j+2)mod3,k+pk(j,ci)×aibk
这里 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;
}

最后的提示

  1. k = 0 k=0 k=0 时, k − 1 k-1 k1 会导致数组越界,需要特判。
  2. 有可能换牌太多导致得分为负数,故初始化为无限小。
  3. 一定要在最后再减换牌带来的扣分。不然就会像作者一样 70pts。
  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值