Description
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到 该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到 多少分吗?
Input
输入文件名 tortoise.in。输入文件的每行中两个数之间用一个空格隔开。
第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。
第2行N个非负整数,a1,a2,……,aN,其中ai表示棋盘第i个格子上的分数。
第3行M个整数,b1,b2,……,bM,表示M张爬行卡片上的数字。
输入数据保证到达终点时刚好用光M张爬行卡片
Output
输出文件名 tortoise.out。
输出只有1行,1个整数,表示小明最多能得到的分数。
Sample Input
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
Sample Output
73
Hint
【输入输出样例 1 说明】
小明使用爬行卡片顺序为1,1,3,1,2,得到的分数为 6+10+14+8+18+17=73。注意,由于起点是 1,所以自动获得第 1 格的分数6。
【输入输出样例2】
tortoise.in
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
tortoise.out
73
【数据范围】
对于30%的数据有1≤N≤30,1≤M≤12。
对于50%的数据有1≤N≤120,1≤M≤50,且4种爬行卡片,每种卡片的张数不会超过 20。
对于100%的数据有1≤N≤350,1≤M≤120,且4种爬行卡片,每种卡片的张数不会超过40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。
不难想到f[i][n1][n2][n3][n4]表示到第i个格子分别用卡片多少张,发现会爆内存,显然可以优化空间f[i][n1][n2][n3],于是我们就得到如下代码:
#include<bits/stdc++.h>
using namespace std;
#define Inc(i,L,r) for(register int i=(L);i<=(r);++i)
const int N = 357;
int n,m,a[N],num[5];
int f[N][41][41][41];
int main(){
cin>>n>>m;
Inc(i,1,n)cin>>a[i];
for(int i=1,x;i<=m;++i)cin>>x,++num[x];
f[1][0][0][0]=a[1];
Inc(i,2,n)//第i格
Inc(tot,1,m)//总共用多少张卡片
for(int n1=0;n1<=num[1]&&n1*1<=i;++n1)//1卡片用n1张
for(int n2=0;n2<=num[2]&&n1*1+n2*2<=i;++n2)
for(int n3=0;n3<=num[3]&&n1*1+n2*2+n3*3<=i;++n3)
if((tot-n1-n2-n3>=0)&&(tot-n1-n2-n3<=num[4])&&(n1*1+n2*2+n3*3+(tot-n1-n2-n3)*4<=i)){
f[i][n1][n2][n3]=-(1<<30);
if(n1)f[i][n1][n2][n3]=max(f[i][n1][n2][n3],f[i-1][n1-1][n2][n3]);
if(n2)f[i][n1][n2][n3]=max(f[i][n1][n2][n3],f[i-2][n1][n2-1][n3]);
if(n3)f[i][n1][n2][n3]=max(f[i][n1][n2][n3],f[i-3][n1][n2][n3-1]);
if(tot-n1-n2-n3)f[i][n1][n2][n3]=max(f[i][n1][n2][n3],f[i-4][n1][n2][n3]);
if(f[i][n1][n2][n3]>=0)f[i][n1][n2][n3]+=a[i];
}
cout<<f[n][num[1]][num[2]][num[3]]<<"\n";
return 0;
}
但是发现会有许多无用状态,而且350*120*40*40*40也会tle飞,于是我们想到要主动扩展状态而不是被动检验~,然后就有了记忆化搜索版本的dp。
#include<bits/stdc++.h>
using namespace std;
#define Inc(i,L,r) for(register int i=(L);i<=(r);++i)
const int N = 355;
int n,m,a[N],num[5];
int f[N][41][41][41];
inline int dp(int p,int n1,int n2,int n3){
if(f[p][n1][n2][n3])return f[p][n1][n2][n3];
if(p==n)return f[p][n1][n2][n3]=a[p];
int tmp=0;
if(n1<num[1])tmp=max(tmp,dp(p+1,n1+1,n2,n3));
if(n2<num[2])tmp=max(tmp,dp(p+2,n1,n2+1,n3));
if(n3<num[3])tmp=max(tmp,dp(p+3,n1,n2,n3+1));
if((p-n1*1-n2*2-n3*3)/4<num[4])tmp=max(tmp,dp(p+4,n1,n2,n3));
return f[p][n1][n2][n3]=tmp+a[p];
}
int main(){
cin>>n>>m;
Inc(i,1,n)cin>>a[i];
for(int i=1,x;i<=m;++i)cin>>x,++num[x];
cout<<dp(1,0,0,0)<<"\n";
return 0;
}