一、算法分析
寒假前曾信誓旦旦说要多练DP,还立了帖子要做一百道,现在继续第二道吧。。。
题目很好理解,状态的保存和转移都比较直观,刚开始打算在状态的DP数组加一个步数维度,表示走到第几个格子,但是这样就成了五维数组了,显然会爆空间。后来发现没有必要,当前格子由当前使用的牌的数目完全导出。所以平时解DP问题时,开始可能想到的状态空间很复杂,这个没关系,但是在实现之前一定要进行优化,考察各个维度是否是线性无关的。
分析到这里这个问题已经得到了解决,(详见代码)。但是如果本题对于空间的要求更加严格的话,事实上我们还可以进行一下滚动数组优化,再压缩一个维度。(其实这个滚动数组优化这道题是在别人的题解中发现的思想,但是别人的题解的写法笔者看不明白,所以就做了几道其它的滚动数组优化的DP,再用自己的写法写了本题的滚动数组优化,个人感觉相对好懂一些吧。)
其他大佬的题解直接把第一个维度压掉了,笔者的代码是把第一个维度压到2。
二、代码及注释
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
const int maxn=42;
int a[355];
int b[maxn];
int ans;
int f[2][maxn][maxn][maxn]; //表示每种牌用了几张,对第一个维度进行滚动数组优化
int cnt[5];
int main(){
scanf("%d%d",&n,&m);
int x;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);//每个位置的得分
for(int i=1;i<=m;i++){
scanf("%d",&x);
cnt[x]++; //记录每种卡片多少张
}
f[0][0][0][0]=a[1]; //起点
for(int i=0;i<=cnt[1];i++){
int pos=i&1; //滚动数组
int pre=(i&1)^1;
for(int j=0;j<=cnt[2];j++)
for(int k=0;k<=cnt[3];k++)
for(int l=0;l<=cnt[4];l++){
int pp=a[i+j*2+k*3+l*4+1];//当前位置得分
if(i) f[pos][j][k][l]=max(f[pos][j][k][l],f[pre][j][k][l]+pp);
if(j) f[pos][j][k][l]=max(f[pos][j][k][l],f[pos][j-1][k][l]+pp);
if(k) f[pos][j][k][l]=max(f[pos][j][k][l],f[pos][j][k-1][l]+pp);
if(l) f[pos][j][k][l]=max(f[pos][j][k][l],f[pos][j][k][l-1]+pp);
}
}
printf("%d",max(f[0][cnt[2]][cnt[3]][cnt[4]],f[1][cnt[2]][cnt[3]][cnt[4]]));
return 0;
}