记忆化搜索(树状)+区间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;
}