打砖块 题解

题目链接

\(50 pts\)

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

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

N Y
N Y

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

\(100pts\)

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

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

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

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

\(f_{i,j}\) 表示前 \(i\) 列用 \(j\) 子弹的最大分数,\(g_{i,j}\) 表示后 \(i\) 列用 \(j\) 子弹的最大分数。对于二者直接分组背包,时间复杂度 \(O(n^3)\)

\(f_{i-1}\)\(g_{i+1}\) 合并(排除掉第 \(i\) 列)只需枚举 \(f\) 用的子弹数和 \(g\) 用的子弹数,时间复杂度 \(O(n^2)\)

总结:本解法在非 \(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 @ 2024-06-08 16:59  2017BeiJiang  阅读(8)  评论(0编辑  收藏  举报