打砖块 题解
对于没有 砖的情况,可以用分组背包解决,算出每一列打 块砖需要的子弹以及对分数的贡献,按照分组背包即可。
对于包含 砖的情况,不能直接分组背包解决。这实际上是打的顺序问题,比如:
N Y N Y
如果手上有两枚子弹,最优策略是先打掉第二列,再打掉第一列;但分组背包的思路是:在打第二列的时候,由于至少需要一个,所以第一列也只能用一枚子弹打,也就是:第一列打了一个,第二列打了两个,显然不优。
我们发现,打一个 砖块的要求和影响分别是:手中必须握有至少一枚子弹,打完后子弹总数没有减少。
可以将列分为最后打的列(显然只有一列)和不是最后打的列,对于不是最后打的列,用 枚子弹去打,这时就可以直接跑分组背包,少的那枚子弹藏在手里,当遇到 砖时需要至少一枚子弹时就拿出来,由于遇到 砖子弹数不会减少,所以这一枚子弹始终存在。
对于最后打的列:枚举剩下的子弹(加上手中的一枚),按照 状态和这一列产生的贡献直接计算即可。
那么在枚举最后打的列时,如何快速计算其他列做背包的值呢?可以用前后缀合并的思想做。
设 表示前 列用 子弹的最大分数, 表示后 列用 子弹的最大分数。对于二者直接分组背包,时间复杂度 。
将 和 合并(排除掉第 列)只需枚举 用的子弹数和 用的子弹数,时间复杂度 。
总结:本解法在非 的地方进行了转化,从而简化了 难度。
具体看代码:
#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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!