Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目描述
来自洛谷社区
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 的矩阵,矩阵中的每个元素 均为非负整数。游戏规则如下:
每次取数时须从每行各取走一个元素,共 个。经过 次后取完矩阵内所有元素; 每次取走的各个元素只能是该元素所在行的行首或行尾; 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值 ,其中 表示第 次取数(从 开始编号); 游戏结束总得分为 次取数得分之和。 帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入格式 输入文件包括 行:
第一行为两个用空格隔开的整数 和 。
第 行为 矩阵,其中每行有 个用单个空格隔开的非负整数。
输出格式
输出文件仅包含 1 行,为一个整数,即输入矩阵取数后的最大得分。
输入
2 3
1 2 3
3 4 2
输出
82
数据范围:
的数据满足:,答案不超过 。
的数据满足:。
思路解答
任意一次取数过程都会从第一行到最后一行挨个取一个数。看上去矩阵的行与行之间有联系,但是实际上是没有任何联系的!
什么意思呢?就是说如果我们换个说法:我们分别对第一行做m次取数过程,再对第二行做m次取数过程……一直到对第n行做m次取数过程。这样做最后和题目是完全等价的。
因此我们只需要把行固定,只在意矩阵的这一行该怎么做状态转移即可。
所以怎么做呢:
由于每次只从两边取数,考虑区间dp。
令dp[i][l][r]为第i行只剩l-r区间有数字时的最大得分。
显然[l,r]区间是由[l-1,r]取掉左边的数字,或者[l,r+1]去掉右边的数字得到的,两者取最大即可。详情见代码:
代码
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define pp pop_back
using ll=long long;
using db=double;
ll matrix[35][35];
ll dp[35][35][35];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>matrix[i][j];
}
}
for(int i=1;i<=n;i++){
for(int d=m-1,t=1;d>=1;d--,t++){
for(int j=1;j+d-1<=m;j++){
int k=j+d-1;
if(j-1>=1)dp[i][j][k]=dp[i][j-1][k]+matrix[i][j-1]*pow(2,t);
if(k+1<=m)dp[i][j][k]=max(dp[i][j][k+1]+matrix[i][k+1]*(ll)pow(2,t),dp[i][j][k]);
}
}
}
ll res=0;
for(int i=1;i<=n;i++){
ll mx=0;
for(int j=1;j<=m;j++){
mx=max(mx,dp[i][j][j]+matrix[i][j]*(ll)pow(2,m));
}
res+=mx;
}
cout <<res;
}
总结
做区间dp时,一定记得要合理建模,换一个角度往往可以事半功倍。