[NOIP2007 提高组] 矩阵取数游戏
题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 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
样例输入 #1
2 3
1 2 3
3 4 2
样例输出 #1
82
提示
【数据范围】
对于
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}\le1000
0≤ai,j≤1000。
【题目来源】
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篇文章,祭~~