飞行员配对问题(仅求方案总数)
思路:二分图后dinic走起,各边容量为1
代码:
#include<bits/stdc++.h>
#define inf 0x7ff
using namespace std;
int ans,n,n1,s,t,tot=1;
int first[105],dis[105],up[105];
bool vis[105];
queue<int>q;
struct edge
{
int u,v,next,w;
}e[500];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
memset(up,0,sizeof(up));
dis[s]=1;
q.push(s);
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (!dis[e[i].v]&&e[i].w)
q.push(e[i].v),
dis[e[i].v]=dis[k]+1;
}
return dis[t];
}
void flow()
{
int minn=0x7ff;
for (int i=up[t];i;i=up[e[i].u])
minn=min(minn,e[i].w);
ans+=minn;
for (int i=up[t];i;i=up[e[i].u])
e[i].w-=minn,e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
if (x==t) {flow();return;}
for (int i=first[x];i;i=e[i].next)
if (e[i].w&&dis[e[i].v]==dis[x]+1)
up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
scanf("%d%d",&n,&n1);
int x,y;
s=n+1;t=n+2;
while (scanf("%d%d",&x,&y)!=EOF)
add(x,y,1),
add(y,x,0),
vis[x]=1;
for (int i=1;i<=n;i++)
if (vis[i]) add(s,i,1),add(i,s,0);
else add(i,t,1),add(t,i,0);
while (bfs())
dfs(s,0x7ff);
printf("%d",ans);
}
太空飞行计划
思路:(在codevs上调了好久,最终弃疗)
cogs上是不用输出收益为0方案,所以我们直接学习了最大权闭合子图的姿势,求出最大流,并用所有点的正权值减去它就可以了
代码:
#include<bits/stdc++.h>
#define inf 0x7fffff
using namespace std;
int ans,n,m,tot=1,s,t;
int first[205],dis[205],up[205];
bool mac[102],exps[102];
queue<int>q;
struct edge
{
int u,v,w,next;
}e[40000];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
memset(up,0,sizeof(up));
q.push(s);
dis[s]=1;
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&!dis[e[i].v])
dis[e[i].v]=dis[k]+1,q.push(e[i].v);
}
return dis[t];
}
void flow()
{
int minn=inf;
for (int i=up[t];i;i=up[e[i].u])
minn=min(minn,e[i].w);
ans-=minn;
for (int i=up[t];i;i=up[e[i].u])
e[i].w-=minn,
e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
if (x==t) {flow();return;}
for (int i=first[x];i;i=e[i].next)
if (e[i].w&&dis[e[i].v]==dis[x]+1)
up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
scanf("%d%d",&m,&n);
s=m+n+1;t=m+n+2;
int x;
for (int i=1;i<=m;i++)
{
scanf("%d",&x);
ans+=x;
add(s,i+n,x);
add(i+n,s,0);
while (1)
{
scanf("%d",&x);
add(i+n,x,inf);
add(x,i+n,0);
if (getchar()!=‘ ‘) break;
}
}
for (int i=1;i<=n;i++)
scanf("%d",&x),add(i,t,x),add(t,i,0);
while (bfs()) dfs(s,inf);
for (int i=first[s];i;i=e[i].next)
if (dis[e[i].v]) exps[e[i].v-n]=1;
for (int i=first[t];i;i=e[i].next)
if (dis[e[i].v]) mac[e[i].v]=1;
for (int i=1;i<=m;i++)
if (exps[i]) printf("%d ",i);
puts("");
for (int i=1;i<=n;i++)
if (mac[i]) printf("%d ",i);
puts("");
printf("%d",ans);
}
圆桌问题
思路:这个构图太简单……不说了
注意:
1.时间上的优化!我之前写的这些都是扯淡!
2.别上codevs评测,没有SPJ
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,tot=1,ans,sum;
int first[500],dis[500],num[500][500],up[500],cur[500],cnt[500];
queue<int>q;
struct edge
{
int u,v,next,w;
}e[100000];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
dis[s]=1;
q.push(s);
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (!dis[e[i].v]&&e[i].w) dis[e[i].v]=dis[k]+1,q.push(e[i].v);
}
if (dis[t])
for (int i=1;i<=t;i++) cur[i]=first[i];
return dis[t];
}
void flow()
{
int minn=0x7fffff;
for (int i=up[t];i;i=up[e[i].u]) minn=min(minn,e[i].w);
if (!minn) return;
ans+=minn;
for (int i=up[t];i;i=up[e[i].u])
{
e[i].w-=minn;e[i^1].w+=minn;
if (e[i].u<=m&&e[i].v<=n+m)
num[e[i].u][e[i].v-m]^=1;
else if (e[i].v<=m&&e[i].u<=n+m)
num[e[i].v][e[i].u-m]^=1;
}
}
int dfs(int x,int maxn)
{
if (x==t) {flow();return maxn;}
int used=0;
for (int i=cur[x];i;i=e[i].next)
if (dis[e[i].v]==dis[x]+1)
{
up[e[i].v]=i;
int flow=dfs(e[i].v,min(maxn,e[i].w));
used+=flow;
if (e[i].w) cur[x]=i;
if (used==maxn) return maxn;
}
return used;
}
main()
{
scanf("%d%d",&m,&n);
int x;
s=n+m+1;t=n+m+2;
for (int i=1;i<=m;i++)
scanf("%d",cnt+i),
sum+=cnt[i],
add(s,i,cnt[i]),
add(i,s,0);
for (int i=1;i<=n;i++)
scanf("%d",&x),
add(i+m,t,x),
add(t,i+m,0);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
add(i,j+m,1),
add(j+m,i,0);
while (bfs()) dfs(s,0x7ffff);
if (ans<sum) {printf("0");return 0;}
puts("1");
for (int i=1;i<=m;i++)
{
for (int j=1;j<=n;j++)
if (num[i][j]) printf("%d ",j);
puts("");
}
}
最长上升子序列问题
思路:发现网上大部分都是最大流拆点,而我写了个半残的费用流……
1.s->i连容量为1,费用为-1的边
2.i->t连容量为1,费用为0的边
3.i->j(a[i]<a[j])连容量为1,费用为-1的边
跑最小费用即可,算长度的话只用记录每次增广路上的费用总和就行了(不要乘流量,单算费用)
不用DP干掉1,2问
第3问把s->1,s->n,1->t,n->t的边容量无穷大就行了
注意:
求的是最长不下降子序列!不是最长上升子序列!这里坑爆了!
#include<bits/stdc++.h>
using namespace std;
int n,a[510],s,t,tot=1,len,ans=1;
int first[510],up[510],dis[510];
bool vis[510];
queue<int> q;
struct edge
{
int u,v,w,next,cost;
}e[200000];
void add(int x,int y,int z,int c)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].cost=c;
e[tot].next=first[x];
first[x]=tot;
}
bool spfa()
{
memset(dis,63,sizeof(dis));
memset(up,0,sizeof(up));
dis[s]=0;vis[s]=1;
q.push(s);
while (!q.empty())
{
int k=q.front();
q.pop();
vis[k]=0;
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&dis[e[i].v]>dis[k]+e[i].cost)
{
dis[e[i].v]=dis[k]+e[i].cost;
up[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,q.push(e[i].v);
}
}
return dis[t]<0x7ffff;
}
int flow()
{
int p=0,minn=0x7fffff;
for (int i=up[t];i;i=up[e[i].u])
minn=min(e[i].w,minn),
p+=e[i].cost;
for (int i=up[t];i;i=up[e[i].u])
e[i].w-=minn,
e[i^1].w+=minn;
return p;
}
main()
{
scanf("%d",&n);
s=n+1;t=n+2;
for (int i=1;i<=n;i++)
{
scanf("%d",a+i);
add(s,i,1,-1);
add(i,s,0,1);
add(i,t,1,0);
add(t,i,0,0);
for (int j=1;j<i;j++)
if (a[j]<=a[i])
add(j,i,1,-1),
add(i,j,0,1);
}
for (int i=tot+1;i<=tot*2;i++) e[i]=e[i-tot];
while (spfa())
{
if (!len) len=flow();
else if (len!=flow()) break;
else ans++;
}
printf("%d\n%d\n",-len,ans);
for (int i=1;i<=tot;i++)
{
e[i]=e[i+tot];
if ((e[i].u==s&&e[i].v==n)||(e[i].u==s&&e[i].v==1)) e[i].w=0x7ff;
if ((e[i].u==n&&e[i].v==t)||(e[i].u==1&&e[i].v==t)) e[i].w=0x7ff;
}
ans=1;len=0;
while (spfa())
{
if (!len) len=flow();
else if (len!=flow()) break;
else ans++;
}
printf("%d",ans);
}
最小路径覆盖问题
思路:
二分图最大流,答案=点数-流量和
方案数从1-n枚举dfs找路径即可,要求流量为0
注意:输出格式注意!最后要换行!codevs上只用输出方案数!
#include<bits/stdc++.h>
using namespace std;
int n,m,tot=1,s,t;
int first[320],dis[320],cur[320],ans;
int num[320];
bool vis[320];
queue<int>q;
struct edge
{
int u,v,w,next;
}e[15000];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
dis[s]=1;
q.push(s);
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&!dis[e[i].v])
dis[e[i].v]=dis[k]+1,
q.push(e[i].v);
}
if (dis[t])
for (int i=1;i<=t;i++) cur[i]=first[i];
return dis[t];
}
int dfs(int x,int maxn)
{
if (x==t) {return maxn;}
int used=0;
for (int i=cur[x];i;i=e[i].next)
if (dis[e[i].v]==dis[x]+1)
{
int k=dfs(e[i].v,min(maxn,e[i].w));
e[i].w-=k;e[i^1].w+=k;
used+=k;
if (e[i].w) cur[x]=i;
if (used==maxn) return maxn;
}
if (!used) dis[x]=0;
return used;
}
void find(int x)
{
num[++num[0]]=x;
vis[x]=1;
for (int i=first[x];i;i=e[i].next)
if (!e[i].w&&e[i].v!=s&&!vis[e[i].v-n]) find(e[i].v-n);
}
main()
{
freopen("path3.in","r",stdin);
freopen("path3.out","w",stdout);
scanf("%d%d",&n,&m);
int x,y;
for (int i=1;i<=m;i++)
scanf("%d%d",&x,&y),
add(x,y+n,1),
add(y+n,x,0);
s=(n<<1)+1;t=n+1<<1;
for (int i=1;i<=n;i++)
add(s,i,1),
add(i,s,0),
add(i+n,t,1),
add(t,i+n,0);
while (bfs())
ans+=dfs(s,0x7ff);
for (int i=1;i<=n;i++)
if (!vis[i])
{
num[0]=0,find(i);
for (int i=1;i<=num[0];i++)
printf("%d%c",num[i]," \n"[i==num[0]]);
}
printf("%d\n",n-ans);
}
餐巾计划问题
思路:
感觉好厉害,一开始没有考虑流量守恒,建图完全错了,该连源点的连汇点,该连汇点的连了源点(s连新餐巾,t连旧餐巾)然后就一直买啊买也不洗……改一下就连买都不买了……后来看了高大哥的课件建图,感觉自己还是功力不深啊!!
代码:
#include<bits/stdc++.h>
#define inf 0x7ffff
using namespace std;
int n,f,c1,c2,t1,t2,s,t,tot=1,ans;
bool vis[2010];
int dis[2010],first[2010],up[2010];
queue<int>q;
struct edge
{
int u,v,w,cost,next;
}e[200000];
void add(int x,int y,int z,int c)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].cost=c;
e[tot].next=first[x];
first[x]=tot;
}
bool spfa()
{
memset(dis,63,sizeof(dis));
memset(up,0,sizeof(up));
q.push(s);
dis[s]=0;vis[s]=1;
while (!q.empty())
{
int k=q.front();
q.pop();
vis[k]=0;
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&dis[e[i].v]>dis[k]+e[i].cost)
{
dis[e[i].v]=dis[k]+e[i].cost;
up[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,q.push(e[i].v);
}
}
return dis[t]<inf;
}
void flow()
{
int minn=inf;
for (int i=up[t];i;i=up[e[i].u]) minn=min(e[i].w,minn);
for (int i=up[t];i;i=up[e[i].u])
e[i].w-=minn,
e[i^1].w+=minn,
ans+=minn*e[i].cost;
}
main()
{
// scanf("%d%d%d%d%d%d",&n,&f,&t1,&c1,&t2,&c2);codevs上的
// scanf("%d%d%d%d%d%d",&n,&t1,&t2,&f,&c1,&c2);
// t1++;t2++;BZOJ上的
s=(n<<1)+1;t=1+n<<1;
int x;
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
add(i+n,t,x,0);
add(t,i+n,0,0);
add(s,i,x,0);
add(i,s,0,0);
add(s,i+n,inf,f);
add(i+n,s,0,-f);
if (i+1<=n) add(i,i+1,inf,0),add(i+1,i,0,0);
if (i+t1<=n) add(i,i+t1+n,inf,c1),add(i+t1+n,i,0,-c1);
if (i+t2<=n) add(i,i+t2+n,inf,c2),add(i+t2+n,i,0,-c2);
}
while (spfa()) flow();
printf("%d",ans);
}
星际转移问题(家园)
思路:数据比较小,可以枚举时间动态开点连边,把图分层,各个太空站在每一个时刻都是一个独立的点,样例中的
2 2 1
1 3 0 1 2
1 3 1 2 –1
构图为这样(没有连边的原因是画出来就很乱,所以不连了)
如果我们把时刻为t时的i点标号为st[t][i]且在这个飞船的下一站为next[i],我们要连的就是(st[t][i],st[t+1][next[i]])
同时,每个太空站是可以留人的,所以还要连(st[t][i],st[t+1][i]
然后枚举时间由1->+∞跑最大流,当总流量>=t时就可以停下了
注意:
1.我们不可能一直把时间枚举下去,差不多t=500(可能更小)的时候就可以停下输出无解了
2.给点标号着实让我蛋疼了好久,大家自己感受下吧
#include<bits/stdc++.h>
#define inf 0x7fff
using namespace std;
int ans,times,n,m,k,tot=1,s,t;
int cur[5000],dis[5000],first[5000],p[30],st[30][17],num[30];
queue<int>q;
struct edge
{
int u,v,w,next;
}e[20000];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
q.push(s);dis[s]=1;
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (!dis[e[i].v]&&e[i].w)
dis[e[i].v]=dis[k]+1,q.push(e[i].v);
}
cur[s]=first[s];cur[t]=first[t];
if (dis[t]) for (int i=1;i<=times*m;i++) cur[i]=first[i];
return dis[t];
}
int dfs(int x,int maxn)
{
if (x==t) return maxn;
int used=0;
for (int i=cur[x];i;i=e[i].next)
if (dis[e[i].v]==dis[x]+1)
{
int k=dfs(e[i].v,min(maxn,e[i].w));
used+=k;
e[i].w-=k;
e[i^1].w+=k;
if(e[i].w) cur[x]=i;
if (used==maxn) return maxn;
}if (!used) dis[x]=0;
return used;
}
main()
{
scanf("%d%d%d",&n,&m,&k);
s=4999;t=4998;
for (int i=1;i<=m;i++)
{
scanf("%d%d",p+i,num+i);
for (int j=0;j<num[i];j++)
{
scanf("%d",st[i]+j);
if (st[i][j]<=0) st[i][j]+=4999;
}
}
while (++times<=500)
{
for (int i=1;i<=m;i++)
{
int x,y;
if (st[i][(times-1)%num[i]]==s) x=s;
else if (st[i][(times-1)%num[i]]==t) x=t;
else x=st[i][(times-1)%num[i]]+n*(times-1);
if (st[i][times%num[i]]==s) y=s;
else if (st[i][times%num[i]]==t) y=t;
else y=st[i][times%num[i]]+n*times;
add(x,y,p[i]);
add(y,x,0);
}
for (int i=n*(times-1)+1;i<=n*(times-1)+n;i++)
add(i,i+n,inf),add(i+n,i,0);
while (bfs()) ans+=dfs(s,0x7ff);
if (ans>=k) printf("%d",times),exit(0);
}
printf("0");
}
骑士共存问题
思路:根据所在行列之和x+y的奇偶来进行二分图,然后跑dinic
s->奇 容量为1
偶数->t 容量为1
奇数->被他影响的8个点 容量为inf
最后得出的最大流就是放完最多数后的不能放的点(不包括m个点),答案就是n*n-m-maxflow
代码:
#include<bits/stdc++.h>
#define inf 0x7ffff
#define pd(x,y,z) (x<=y&&y<=z)
using namespace std;
int ans,s,t,n,m,x,y,tot=1;
int dis[80010],first[80010],cur[80010];
int dx[9]={0,-1,-2,-2,-1,1,2,2,1},dy[9]={0,-2,-1,1,2,-2,-1,1,2};
bool vis[80010];
queue<int>q;
struct edge
{
int u,v,w,next;
}e[410000];
void add(int x,int y,int z)
{
e[++tot]=(edge){x,y,z,first[x]};
first[x]=tot;
}
bool bfs()
{
memset(dis,0,sizeof(dis));
q.push(s);dis[s]=1;
while (!q.empty())
{
int k=q.front();
q.pop();
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&!dis[e[i].v])
q.push(e[i].v),dis[e[i].v]=dis[k]+1;
}
if (dis[t])
for (int i=1;i<=t;i++) cur[i]=first[i];
return dis[t];
}
int dfs(int x,int maxn)
{
if (x==t) return maxn;
int used=0;
for (int i=cur[x];i;i=e[i].next)
if (dis[e[i].v]==dis[x]+1)
{
int k=dfs(e[i].v,min(maxn,e[i].w));
e[i].w-=k;
e[i^1].w+=k;
if (e[i].w) cur[x]=i;
used+=k;
if (used==maxn) return maxn;
}
if (!used) dis[x]=0;
return used;
}
main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d%d",&x,&y),
vis[(x-1)*n+y]=1;
s=n*n+1;t=n*n+2;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (!vis[(i-1)*n+j])
{
x=(i-1)*n+j;
if ((i+j)&1)
{
add(s,x,1);add(x,s,0);
for (int k=1;k<=8;k++)
if (pd(1,i+dx[k],n)&&pd(1,j+dy[k],n))
add(x,n*(i+dx[k]-1)+j+dy[k],inf),
add(n*(i+dx[k]-1)+j+dy[k],x,0);
}
else add(x,t,1),add(t,x,0);
}
while (bfs())
ans+=dfs(s,inf);
printf("%d",n*n-ans-m);
}
魔术球问题(简化版)
思路:枚举答案,动态加点判断,但一开始我是暴力加边,多建了一层关于“柱子”的点,只过了9个点,后来看到黄学长的blog才幡然醒悟,合并点,这里直接求最小路径覆盖就好了,每次加入一个待放的球,跑最大流,判断一下,如果它不能归到之前的路径上就让ans++,直到ans>n
注意:codevs上没有SPJ,但要输出方案数!
#include<bits/stdc++.h>
using namespace std;
int n,s,t,tot=1,ans=1;
struct edge
{
int u,v,w,next;
}e[200000];
int first[3500],dis[3500];
bool sr[3204];
queue<int>q;
void add(int x,int y,int z){e[++tot]=(edge){x,y,z,first[x]};first[x]=tot;}
bool bfs()
{
memset(dis,0,sizeof(dis));
q.push(s);dis[s]=1;
while (!q.empty())
{
int k=q.front();
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&!dis[e[i].v])
q.push(e[i].v),dis[e[i].v]=dis[k]+1;
q.pop();
}
return dis[t];
}
int dfs(int x,int maxn)
{
if (x==t) return maxn;
int used=0;
for (int i=first[x];i;i=e[i].next)
if (dis[e[i].v]==dis[x]+1)
{
int k=dfs(e[i].v,min(maxn,e[i].w));
e[i].w-=k;e[i^1].w+=k;
used+=k;
if (used==maxn) return maxn;
}
if (!used) dis[x]=0;
return used;
}
main()
{
freopen("balla.in","r",stdin);
freopen("balla.out","w",stdout);
for (int i=1;i<=56;i++) sr[i*i]=1;
t=3244;
scanf("%d",&n);
for (int i=1;i<=1600;i++,ans++)
{
add(s,i,1);add(i,s,0);
for (int j=1;j<i;j++)
if (sr[j+i]) add(j,i+1600,1),add(i+1600,j,0);
add(i+1600,t,1);
add(t,i+1600,0);
while (bfs()) ans-=dfs(s,0x7f);
if (ans>n) {ans=i;break;}
}
printf("%d",ans-1);
}
方格取数问题
思路:二分图染黑白,然后就是求最大权闭合子图了,可以参照上面的太空飞行计划,流量就是点的权值
不过这道题好坑啊,之前的最大流方法总会WA三个点,只能用回最暴力的dfs单线跑流了
代码:
#include<bits/stdc++.h>
#define inf 0x7fffff
#define pd(x,y,z) (x<=y&&y<=z)
using namespace std;
int n,m,s,t,tot=1,sum,ans;
int dis[1000],first[1000],cur[1000],up[1000];
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
queue<int>q;
struct edge
{
int u,v,w,next;
}e[10000];
void add(int x,int y,int z) {e[++tot]=(edge){x,y,z,first[x]};first[x]=tot;}
bool bfs()
{
memset(dis,0,sizeof(dis));
dis[s]=1;q.push(s);
while (!q.empty())
{
int k=q.front();
for (int i=first[k];i;i=e[i].next)
if (e[i].w&&!dis[e[i].v])
q.push(e[i].v),dis[e[i].v]=dis[k]+1;
q.pop();
}
if (dis[t])
for (int i=s;i<=t;i++) cur[i]=first[i];
return dis[t];
}
void flow()
{
int minn=inf;
for (int i=up[t];i;i=up[e[i].u])
minn=min(minn,e[i].w);
ans+=minn;
for (int i=up[t];i;i=up[e[i].u])
e[i].w-=minn,e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
if (x==t) {flow();return;}
for (int i=first[x];i;i=e[i].next)
if (e[i].w&&dis[e[i].v]==dis[x]+1)
up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
scanf("%d%d",&n,&m);
int x;
t=n*m+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
scanf("%d",&x);
sum+=x;
if (((i+j)&1))
{
add(s,m*(i-1)+j,x);
add(m*(i-1)+j,s,0);
for (int k=0;k<4;k++)
if(pd(1,i+dx[k],n)&&pd(1,j+dy[k],m))
add(m*(i-1)+j,m*(i-1+dx[k])+j+dy[k],inf),
add(m*(i-1+dx[k])+j+dy[k],m*(i-1)+j,0);
}
else add(m*(i-1)+j,t,x),add(t,m*(i-1)+j,0);
}
while (bfs()) dfs(s,inf);
printf("%d",sum-ans);
}