题目
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;
}