1.题目
题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 n × m n\times m n×m 的矩阵,矩阵中的每个元素 a i , j a_{i,j} ai,j 均为非负整数。游戏规则如下:
- 每次取数时须从每行各取走一个元素,共 n n n 个。经过 m m m 次后取完矩阵内所有元素;
- 每次取走的各个元素只能是该元素所在行的行首或行尾;
- 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值 × 2 i \times 2^{i} ×2i ,其中 i i i 表示第 i i i 次取数(从 1 1 1 开始编号);
- 游戏结束总得分为 m m m 次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入格式
输入文件包括
n
+
1
n+1
n+1 行:
第一行为两个用空格隔开的整数
n
n
n 和
m
m
m 。
第
2
∼
n
+
1
2 \sim n+1
2∼n+1 行为
n
×
m
n \times m
n×m矩阵,其中每行有
m
m
m 个单个空格隔开的非负整数。
输出格式
输出文件仅包含 1 1 1 行,为一个整数,即输入矩阵取数后的最大得分。
输入输出样例
输入 #1
2 3
1 2 3
3 4 2
输出 #1
82
说明/提示
NOIP 2007 提高第三题
数据范围:
60
%
60\%
60% 的数据满足:
1
≤
n
,
m
≤
30
1 \le n,m \le 30
1≤n,m≤30 ,答案不超过
1
0
16
10^{16}
1016 。
100
%
100\%
100% 的数据满足:
1
≤
n
,
m
≤
80
1 \le n,m \le 80
1≤n,m≤80 ,
0
≤
a
i
,
j
≤
1000
0 \le a_{i,j} \le 1000
0≤ai,j≤1000 。
2.思路
主要思路:动态规划。由于各行取数的得分互不影响,则游戏总得分可以这样计算:分别对每行按照题目规则取数,共取 m m m 次,并计算该行的总得分,最后将所有行的总得分相加,即为游戏总得分。在对每行进行取数时,就需要采用动态规划的思路来计算最优解。
- 步骤一:定义动态规划数组
d
p
dp
dp
由于取数时只能从该行的最左侧或最右侧取值,故定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示取数区间变为 [ i , j ] [i,j] [i,j] 时的最优解, i i i 是取数区间的最左侧, j j j 是取数区间的最右侧。 - 步骤二:定义状态转移方程
取数区间 [ i , j ] [i,j] [i,j] 是由区间 [ i − 1 , j ] [i-1,j] [i−1,j] 取最左侧的数或 [ i , j + 1 ] [i, j+1] [i,j+1] 取最右侧的数之后变成的,则状态转移方程为 d p [ i ] [ j ] = max { d p [ i − 1 , j ] + a k , i − 1 × 2 m − j + i − 1 , d p [ i , j + 1 ] + a k , j + 1 × 2 m − j + i − 1 } dp[i][j] = \max\{dp[i-1,j]+a_{k,i-1}\times 2^{m-j+i-1},dp[i,j+1]+a_{k,j+1}\times 2^{m-j+i-1}\} dp[i][j]=max{dp[i−1,j]+ak,i−1×2m−j+i−1,dp[i,j+1]+ak,j+1×2m−j+i−1}其中的 k k k 表示第 k k k 行。之所以 2 2 2 的幂次为 m − j + i − 1 m-j+i-1 m−j+i−1 ,是由于其和取数区间的长度有关:当取数区间变为 [ 1 , m ] [1,m] [1,m] 时,此时为第 m − m = 0 m-m=0 m−m=0 次取数,即区间长度 m m m 对应第 0 0 0 次取数;每取一次数,区间长度减一,次数加一,故当取数区间变为 [ i , j ] [i,j] [i,j] 时,为第 m − ( j − i + 1 ) = m − j + i − 1 m-(j-i+1) = m-j+i-1 m−(j−i+1)=m−j+i−1 次取数。另外,最终取数区间将变成 [ i , i ] [i,i] [i,i] 的形式,对应 d p [ i ] [ i ] dp[i][i] dp[i][i] ,但并未加上 a k , i × 2 m a_{k,i} \times 2^m ak,i×2m ,因此,最终的最优解应从所有的 d p [ i ] [ i ] + a k , i × 2 m dp[i][i]+a_{k,i} \times 2^m dp[i][i]+ak,i×2m 中选择最大值,即 m a x = max 1 ≤ i ≤ m { d p [ i ] [ i ] + a k , i × 2 m } max=\max_{1 \le i \le m}\{dp[i][i]+a_{k,i} \times 2^m\} max=1≤i≤mmax{dp[i][i]+ak,i×2m}这仅仅是第 k k k 行的最优解,游戏总得分应为所有行最优解之和。 - 步骤三:设置初始值
对于矩阵中的一行数,最开始的取数区间为 [ 1 , m ] [1,m] [1,m] , d p [ 1 , m ] dp[1,m] dp[1,m] 的值应为 0 0 0 ,因为第 0 0 0 次取数自然取不出大于零的数,初始得分应为 0 0 0,则 d p [ 1 , m ] dp[1,m] dp[1,m] 可以看成取数区间 [ 0 , m ] [0, m] [0,m] 取走一个数 0 0 0 或取数区间 [ 1 , m + 1 ] [1,m+1] [1,m+1] 取走一个数 0 0 0 ,并令 d p [ 0 , m ] = d p [ 1 , m + 1 ] = 0 dp[0,m]=dp[1,m+1]=0 dp[0,m]=dp[1,m+1]=0 这样就与步骤二中的状态转移方程相同。对于区间最左侧为 1 1 1 的取数区间,自然不可能由区间 [ 0 , y ] [0,y] [0,y] 状态转移过来,因此令 d p [ 0 ] [ y ] = 0 dp[0][y]=0 dp[0][y]=0 ,并令所取得的数为 0 0 0 ,以满足步骤二中的状态转移方程。同理,对于区间最右侧为 m m m 的取数区间,自然不可能由区间 [ x , m ] [x,m] [x,m] 状态转移过来,因此令 d p [ x ] [ m ] = 0 dp[x][m]=0 dp[x][m]=0 ,并令所取得的数为 0 0 0 ,以满足步骤二中的状态转移方程。
3.代码
根据提示,最后的答案可能超出long
类型的最大值,因此我们使用Java
中的BigInteger
类来作为动态规划数组等变量的数据类型。代码如下:
import java.math.BigInteger;
import java.util.Scanner;
/**
* @author xiaoyaosheny
* @discription 洛谷P1005 [NOIP2007 提高组] 矩阵取树游戏
* @date 2021/7/10
*/
public class Main {
/**
* 程序入口
*
* @param args 输入参数
*/
public static void main(String[] args) {
// 获取输入
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] matrix = new int[n][m + 2];
for (int i = 0; i < n; i++) {
for (int j = 1; j < m + 1; j++) {
matrix[i][j] = scanner.nextInt();
}
}
// 解决问题
BigInteger score = new BigInteger("0");
for (int j = 0; j < n; j++) {
// 动态规划数组
BigInteger[][] dp = new BigInteger[m + 2][m + 2];
for (int k = 0; k < m + 2; k++)
{
for (int l = 0; l < m + 2; l++)
dp[k][l] = new BigInteger("0");
}
// 动态规划
for (int k = 1; k < m + 1; k++) {
for (int l = m; l >= k; l--)
{
BigInteger base2 = new BigInteger("2").pow(m - l + k - 1);
dp[k][l] = (dp[k - 1][l].add(new BigInteger(String.valueOf(matrix[j][k - 1])).multiply(base2))).max(dp[k][l + 1].add(new BigInteger(String.valueOf(matrix[j][l + 1])).multiply(base2)));
}
}
BigInteger max = new BigInteger("0");
for (int k = 1; k < m + 1; k++) {
max = max.max(dp[k][k].add(new BigInteger(String.valueOf(matrix[j][k])).multiply(new BigInteger("2").pow(m))));
}
score = score.add(max);
}
// 输出结果
System.out.println(score);
}
}