【备战蓝桥杯】12.矩阵取数游戏——区间dp

329 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

来自洛谷社区

帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 n×mn \times m 的矩阵,矩阵中的每个元素 ai,ja_{i,j} 均为非负整数。游戏规则如下:

每次取数时须从每行各取走一个元素,共 nn 个。经过 mm 次后取完矩阵内所有元素; 每次取走的各个元素只能是该元素所在行的行首或行尾; 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值×2i\times 2^i ,其中 ii 表示第 ii 次取数(从 11 开始编号); 游戏结束总得分为 mm 次取数得分之和。 帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

输入格式 输入文件包括 n+1n+1 行:

第一行为两个用空格隔开的整数 nnmm

2n+12\sim n+1 行为 n×mn \times m 矩阵,其中每行有 mm 个用单个空格隔开的非负整数。

输出格式

输出文件仅包含 1 行,为一个整数,即输入矩阵取数后的最大得分。

输入

2 3
1 2 3
3 4 2

输出

82

数据范围:

60%60\% 的数据满足:1n,m301≤n,m≤30,答案不超过 101610^{16}

100%100\% 的数据满足:1n,m800ai,j10001≤n,m≤80,0≤a_{i,j}≤1000

思路解答

任意一次取数过程都会从第一行到最后一行挨个取一个数。看上去矩阵的行与行之间有联系,但是实际上是没有任何联系的!

什么意思呢?就是说如果我们换个说法:我们分别对第一行做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时,一定记得要合理建模,换一个角度往往可以事半功倍。