打砖块 题解

题目链接

50pts

对于没有 Y 砖的情况,可以用分组背包解决,算出每一列打 j 块砖需要的子弹以及对分数的贡献,按照分组背包即可。

对于包含 Y 砖的情况,不能直接分组背包解决。这实际上是打的顺序问题,比如:

N Y
N Y

如果手上有两枚子弹,最优策略是先打掉第二列,再打掉第一列;但分组背包的思路是:在打第二列的时候,由于至少需要一个,所以第一列也只能用一枚子弹打,也就是:第一列打了一个,第二列打了两个,显然不优。

100pts

我们发现,打一个 Y 砖块的要求和影响分别是:手中必须握有至少一枚子弹,打完后子弹总数没有减少。

可以将列分为最后打的列(显然只有一列)和不是最后打的列,对于不是最后打的列,用 k1 枚子弹去打,这时就可以直接跑分组背包,少的那枚子弹藏在手里,当遇到 Y 砖时需要至少一枚子弹时就拿出来,由于遇到 Y 砖子弹数不会减少,所以这一枚子弹始终存在。

对于最后打的列:枚举剩下的子弹(加上手中的一枚),按照 dp 状态和这一列产生的贡献直接计算即可。

那么在枚举最后打的列时,如何快速计算其他列做背包的值呢?可以用前后缀合并的思想做。

fi,j 表示前 i 列用 j 子弹的最大分数,gi,j 表示后 i 列用 j 子弹的最大分数。对于二者直接分组背包,时间复杂度 O(n3)

fi1gi+1 合并(排除掉第 i 列)只需枚举 f 用的子弹数和 g 用的子弹数,时间复杂度 O(n2)

总结:本解法在非 dp 的地方进行了转化,从而简化了 dp 难度。

具体看代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=210;
int n,m,k;
int s[N][N],pd[N][N];
struct node {
int st,v,w;
}; vector<node> a[N];
int f[N][N],g[N][N],dp[N];
int main(){
ios::sync_with_stdio(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
char c; cin>>s[i][j]>>c;
if(c=='Y') pd[i][j]=1;
else pd[i][j]=0;
}
}
for(int j=1;j<=m;j++) {
int st=0,v=0,w=0;
for(int i=n;i>=1;i--) {
v++; w+=s[i][j];
a[j].push_back({v,v-pd[i][j],w});
v-=pd[i][j];
}
}
k--;//在手中留一枚子弹
for(int i=1;i<=m;i++) {//预处理 f
for(auto t:a[i]) {
for(int j=0;j<=k;j++) {
f[i][j]=max(f[i][j],f[i-1][j]);
if(j>=t.v) f[i][j]=max(f[i][j],f[i-1][j-t.v]+t.w);
}
}
}
for(int i=m;i>=1;i--) {//预处理 g
for(auto t:a[i]) {
for(int j=0;j<=k;j++) {
g[i][j]=max(g[i][j],g[i+1][j]);
if(j>=t.v) g[i][j]=max(g[i][j],g[i+1][j-t.v]+t.w);
}
}
}
int maxn=0;
for(int i=1;i<=m;i++) {
memset(dp,0,sizeof(dp));
for(int j=0;j<=k;j++) {//合并f,g
for(int z=0;z<=k;z++) {
if(j+z<=k) dp[j+z]=max(dp[j+z],f[i-1][j]+g[i+1][z]);
}
}
int num=k+1;//将手中的子弹搞回来
for(int j=0;j<=k;j++) {
for(auto t:a[i]) {
if(num-j>=t.st) maxn=max(maxn,dp[j]+t.w);
}
}
}
cout<<maxn;
return 0;
}
posted @   2017BeiJiang  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示