记忆化搜索(树状)+区间dp
/*
思路其实大同小异:按行进行区间DP,
我们可以设区间[L,R]的最大值为f[L][R],
用一个数组p[n]来储存2^n的值,采用记忆化搜索的办法,
设k=m-(R-L),可以得到状态转移方程:f[L][R]=max(num[L]_p[k]+dp(L+1,R),dp(L,R-1)+num[R]_p[k]),
当然由于2^80甚至超出了long long的范围,
如果不想用高精度的话也可以用__int128来存储(如果本地不能编译可以用luogu的在线IDE调试),
要注意的是最后输出的时候要特判答案为0的情况(第一个点).
*/
#include <bits/stdc++.h>
#define i128 __int128
using namespace std;
int n,m;
int nm[85];
i128 pw2[85],ans=0,qdp[85][85];
i128 dp(int a,int b){
if(qdp[a][b]!=-1)return qdp[a][b];
if(b-a>=1){//一行中 还有2个数 或者以上,才能 继续dfs记忆化搜索
qdp[a][b]=max(nm[a]*pw2[m-(b-a)]+dp(a+1,b),dp(a,b-1)+nm[b]*pw2[m-(b-a)]);
/*
如一开始可以去第一行的行首,或者行尾,然后 a b(指的是 a到b区间)
选了行首,则 搜索 a+1 到b范围 选了行尾,则搜索 a到 b-1的范围
因为会重复计算 所以可以用记忆化
如:1-10
左边选一个数 a到2,右边选4 b到6
然后 右选4个数 b到6 再左边选一个数 a到2 是一样的
搜索就像一颗二叉树
*/
}else qdp[a][b]=nm[a]*pw2[m-(b-a)];//如果a==b也就是说 只剩下一个数,直接就运算赋值就好了
return qdp[a][b];
}
void p128(i128 x){
if(!x)return;
if(x) p128(x/10);
printf("%c",x%10+'0');//不能用printf 只能用putchar
//putchar(x%10+'0');
}/*__int128 的输出函数*/
int main(){
scanf("%d%d",&n,&m);
pw2[0]=1;
for(int i=1;i<=m;i++)pw2[i]=pw2[i-1]*2;/*用pw2[i]=1<<i; 第32个位置 会出现字符,导致运算错误,重新计算!*/
//for(int i=1;i<=m;i++){/*printf("%d:",i);*/p128(pw2[i]);/*printf("\n");*/}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)scanf("%d",&nm[j]);
memset(qdp,-1,sizeof(qdp));//初始化 区间dp的数组
ans+=dp(1,m);
}
if(!ans)printf("0");
else p128(ans);
return 0;
}