第一道插头 $dp$
由于讲不清楚所以假装各位早就会插头 $dp$ 了
首先要的是一个闭合回路,所以可以用括号表示法表示状态,然后大力分类讨论
$1.$ 没有右插头和下插头
那么我们可以啥也不干,或者加一个右插头和下插头

$2.$ 只有下插头没有右插头
那么我们可以要把下插头继续延伸,可以向下或者向右


$3.$ 只有右插头没有下插头:
同理我们可以往下或者往右延伸


$4.$ 右插头和下插头都有:这个又要分情况讨论
$a.$ 插头都是左括号,那么要把两个联通还要把右边原来匹配的右括号改成左括号


$b.$ 插头都是右括号,那么要把两个联通还要把左边原本匹配的左括号改成右括号


$c.$ 右插头是左括号,下插头是右括号,那么直接联通即可,不需要改变其他括号


$d.$ 右插头是右括号,下插头是左括号,如果联通则一定形成一个闭合回路,那么如果其他地方没有插头则直接把当前状态计入答案,否则此状态不合法,不用延伸状态(右图是一种不合法状态)


然后根据考虑到的所有状态慢慢转移即可......
因为数据比较大所以用滚动数组和哈希表维护状态,状态在加入哈希表时更新
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
int 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<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
const int N=3e5;
const ll INF=1e18;
int n,m,a[107][107],bit[17],cur,pre;
ll f[2][N+7],Ans=-INF;
int fir[N+7],from[N+7],to[2][N+7],cntt[2];//哈希表
inline void insert(int sta,ll val)//插入状态并更新f
{
int p=sta%N;
for(int i=fir[p];i;i=from[i])
{
if(to[cur][i]!=sta) continue;
f[cur][i]=max(f[cur][i],val); return;
}
from[++cntt[cur]]=fir[p]; fir[p]=cntt[cur];
to[cur][cntt[cur]]=sta; f[cur][cntt[cur]]=val;
}
void DP()
{
cntt[cur]=1; int now,down,right; ll val;
for(int i=1;i<=n;i++)//枚举行
{
for(int j=1;j<=cntt[cur];j++) to[cur][j]<<=2;//每一行末尾,状态统一左移四进制下的一位
for(int j=1;j<=m;j++)//枚举列
{
pre=cur; cur^=1; cntt[cur]=0; memset(fir,0,sizeof(fir));//清空哈希表
for(int k=1;k<=cntt[pre];k++)//枚举上一行的状态
{
now=to[pre][k]; val=f[pre][k];
right=(now>>bit[j-1])%4; down=(now>>bit[j])%4;//提出插头状态
if(!down&&!right)//没有插头
{
insert(now,val);//继续没有插头
if(j!=m) insert(now+(1<<bit[j-1])+((1<<bit[j])<<1),val+a[i][j]);//多一个右插头和下插头
}
if(down&&!right)//只有下插头
{
insert(now-down*(1<<bit[j])+down*(1<<bit[j-1]),val+a[i][j]);//向下延伸
if(j!=m) insert(now,val+a[i][j]);//向右延伸
}
if(!down&&right)//只有右插头
{
insert(now,val+a[i][j]);//向下延伸
if(j!=m) insert(now-right*(1<<bit[j-1])+right*(1<<bit[j]),val+a[i][j]);//向右延伸
}
if(down==1&&right==1)//插头为'(('
{
int cnt=1;
for(int l=j+1;l<=m;l++)//找到右边第一个和当前下插头匹配的')'
{
if((now>>bit[l])%4==1) cnt++;
if((now>>bit[l])%4==2) cnt--;
if(!cnt)
{
insert(now-(1<<bit[l])-(1<<bit[j-1])-(1<<bit[j]),val+a[i][j]);//联通并改')'为'('
break;
}
}
}
if(down==2&&right==2)//插头为'))'
{
int cnt=1;
for(int l=j-2;l>=0;l--)//找到左边第一个和当前右插头匹配的'('
{
if((now>>bit[l])%4==1) cnt--;
if((now>>bit[l])%4==2) cnt++;
if(!cnt)
{
insert(now+(1<<bit[l])-((1<<bit[j-1])<<1)-((1<<bit[j])<<1),val+a[i][j]);//联通并改'('为')'
break;
}
}
}
if(down==1&&right==2)//')('
insert(now-((1<<bit[j-1])<<1)-(1<<bit[j]),val+a[i][j]);//直接联通即可
if(down==2&&right==1)//'()'
if(now==((1<<bit[j])<<1)+(1<<bit[j-1]))//当前状态只有这两个插头
Ans=max(Ans,val+a[i][j]);//统计答案
}
}
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=10;i++) bit[i]=(i<<1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) a[i][j]=read();
DP();
printf("%lld\n",Ans);
return 0;
}