矩阵取数游戏——典型的动态规划问题,值得一看

[NOIP2007 提高组] 矩阵取数游戏

题目描述

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

  1. 每次取数时须从每行各取走一个元素,共 n n n 个。经过 m m m 次后取完矩阵内所有元素;
  2. 每次取走的各个元素只能是该元素所在行的行首或行尾;
  3. 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值 × 2 i \times 2^i ×2i,其中 i i i 表示第 i i i 次取数(从 1 1 1 开始编号);
  4. 游戏结束总得分为 m m m 次取数得分之和。

帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

输入格式

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

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

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

输出格式

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

样例 #1

样例输入 #1

2 3
1 2 3
3 4 2

样例输出 #1

82

提示

【数据范围】

对于 60 % 60\% 60% 的数据,满足 1 ≤ n , m ≤ 30 1\le n,m\le 30 1n,m30,答案不超过 1 0 16 10^{16} 1016
对于 100 % 100\% 100% 的数据,满足 1 ≤ n , m ≤ 80 1\le n,m\le 80 1n,m80 0 ≤ a i , j ≤ 1000 0\le a_{i,j}\le1000 0ai,j1000

【题目来源】

NOIP 2007 提高第三题。

思路

该题看上去是一个平面类问题,但是实际上不是,因为行与行之间的取数没有关系,我们只需要考虑每一行的取数即可。阶段按每次取数来划分。状态:取数之后这一行剩下的情况是不确定了,为了记录当前这一行的状态,我们用dp[i][j]表示取数取到还剩下[i,j]的最优解。状态转移:每次取数可以在左端点,也可以在右端点,如果在左端点取数,那么由dp[i-1][j]转移过来,如果在右端点取数,有dp[i][j+1]转移过来。
这里很明显有问题了,线性DP的做法是按照时间或者空间的先后顺序来求解,并且由已知量推到未知量,但是无论是从左到右还是从右刀座,左右两边的状态一定有一个未知量。该题其实是一道区间DP的题,取数是按照从少到多的顺序而不是按照从前往后的顺序,即序列长度的大小关系。所以我们在枚举区间左端点时从小到大枚举,枚举区间有端点是从大到小枚举,这样就保证了每次状态转移时的状态都是已知的。当区间为[l,r]时,一共有m个数,当前取的是第m-(r-l+1)个数。
那么什么时候表示整个区间的数都取完了呢?区间[l,l]表示还有一个数,所以区间的数取完应该是[l,l-1],而任意一个点都有可能是最终状态。
该题还有更加简单的状态转移过程,取数从两边往中间取或者从中间往两边取其实效果是一样的,只是每次乘的系数(2^n)不一样。该题可以倒过来看,看做把一个长度为0的区间不停的往两边加数最终变成区间[1,m]。而系数问题相当于每次把之前dp[i][j]的值*2再计算。

至于本题的高精度算法,其实我们可以运用一个 _ _ i n t 128 \_\_int128 __int128来解决,而且现在的CCF也将曾经对 _ _ i n t 128 \_\_int128 __int128的封令给取消了,所以我们就可以肆无忌惮的使用啦!
只不过这里要注意一下, _ _ i n t 128 \_\_int128 __int128需要用快读快写来读入和输出

ACcode

#include<bits/stdc++.h>
using namespace std;

__int128 a[100][100],dp[100][100],num[100],maxn;

inline __int128 read(){
    __int128 x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline void write(__int128 x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)
        write(x/10);
    putchar(x%10+'0');
}

int main(){
	num[0]=1;
	for(int i=1;i<=80;i++){
		num[i]=num[i-1]*2;
	}
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			a[i][j]=read();
		}
	}
	for (int k=1;k<=n;k++){
		memset(dp,0,sizeof(dp));
		for (int len=1;len<=m;len++){
			for (int i=1;i+len-1<=m;i++){
				int j=i+len-1;
				dp[i][j]=max(dp[i+1][j]+a[k][i]*num[m-len+1],dp[i][j-1]+a[k][j]*num[m-len+1]);
			}
		}
		maxn+=dp[1][m];
	}
	write(maxn);
	return 0;
}

发表了100篇文章,祭~~

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值