KEYENCE Programming Contest 2021 (AtCoder Beginner Contest 227) F. Treasure Hunting(线性dp)

题目

h*w(h<=30,w<=30)的格子矩阵,第i行第j列格子权值a[i][j](1<=a[i][j]<=1e9)

从(1,1)出发,前往(h,w),

找到一条路径,取途中最大的k(k<h+w)个格子的权值求和后,和是所有路径中最小的

只需输出这个和

思路来源

官方题解 & 潘老师

题解

枚举途中的最小值,那么:

1. 大于最小值的部分,必取

2. 等于最小值的部分,最小值需要至少出现一次,出现多次时可取其中若干次(至少一次)

3. 小于最小值的部分,只能不取,此时认为这条路可以走

因为只要大于等于最小值的个数达到了k个,这条路也是一条合法的路

首先对n*m个权值离散化,枚举途中的最小值,

一开始想的是dp[i][j][x][k]表示,

当前位于(i,j),当前最小值是x,大于等于最小值的权值已经选了k个,权值和最小是多少

但是这样复杂度是O(h*w*h*w*k*h*w)的,不能接受

后来注意到,当枚举最小值时,只需关注枚举的最小值是否出现即可,

终态要么是大于最小值,要么是等于最小值,并且大于最小值的时候的值都是必取的

换言之也就是最小值出没出现过,所以第三维开2即可

dp[i][j][2][k]表示:

当前位于(i,j),最小值是否已经出现过了,大于等于最小值的权值已经选了k个,权值和最小是多少

转移枚举当前是哪一个状态,向右走还是向下走

1. 如果小于等于最小值,可以用不取转移

2. 如果大于等于最小值,可以用取转移

代码

#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
const int N=32,M=N*N,K=2*N;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n,m,k,a[N][N],x[M],id[N][N],c;
ll dp[N][N][2][K];//dp[i][j][最小值是否出现过][大于等于最小值的值选了多少个]
//小于最小值,只能不取
//等于最小值,可取可不取,出现多次时可取其中若干次(至少一次)
//大于最小值,必取
void upd(ll &x,ll y){
    x=min(x,y);
}
int main(){
    sci(n),sci(m),sci(k);
    memset(dp,INF,sizeof dp);
    rep(i,1,n){
        rep(j,1,m){
            sci(a[i][j]);
            x[++c]=a[i][j];
        }
    }
    sort(x+1,x+c+1);
    c=unique(x+1,x+c+1)-(x+1);
    ll ans=INF;
    rep(i,1,n){
        rep(j,1,m){
            id[i][j]=lower_bound(x+1,x+c+1,a[i][j])-x;
            //printf("i:%d j:%d id:%d\n",i,j,id[i][j]);
        }
    }
    rep(y,1,c){//枚举最小值x[y],途中大于等于x[y]的必取,判断最后最小值有没有取到x[y]
        memset(dp,INF,sizeof dp);
        int p=id[1][1];
        if(p>=y)dp[1][1][p==y][1]=a[1][1];//取
        if(p<=y)dp[1][1][0][0]=0;//不取
        rep(i,1,n){
            rep(j,1,m){
                rep(y2,0,1){
                    rep(z,0,k){
                        //printf("y:%d i:%d j:%d y2:%d z:%d dp:%lld\n",y,i,j,y2,z,dp[i][j][y2][z]);
                        if(dp[i][j][y2][z]==INF)continue;
                        if(i+1<=n){
                            int p2=id[i+1][j];
                            if(p2<=y){
                                upd(dp[i+1][j][y2][z],dp[i][j][y2][z]);
                            }
                            if(p2>=y){
                                upd(dp[i+1][j][y2|(p2==y)][z+1],dp[i][j][y2][z]+a[i+1][j]);//大于最小值,必取
                            }
                        }
                        if(j+1<=m){
                            int p2=id[i][j+1];
                            if(p2<=y){
                                upd(dp[i][j+1][y2][z],dp[i][j][y2][z]);
                            }
                            if(p2>=y){
                                upd(dp[i][j+1][y2|(p2==y)][z+1],dp[i][j][y2][z]+a[i][j+1]);//大于最小值,必取
                            }
                        }
                    }
                }
            }
        }
        ans=min(ans,dp[n][m][1][k]);
    }
    printf("%lld\n",ans);
    return 0;
}

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值