Description

高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。
  作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。

Solution

因为只有两种选择,并且选择之间有关系,我们自然想到割。

因为每个割将所有点分成两个集合,总喜悦值减去这个割就是答案,显然就是最小割。

重点在于,如何连边?

暂时先考虑只有两个点x,y的情况,设源点为S选文科,汇点为T选理科。

a表示选文科的喜悦值,b表示选理科的喜悦值,cx,y表示x,y都选文科的值,dx,y表示都选理科,W表示边权

[JZOJ1919] [BZOJ2127]【2011集训队出题】happiness(最小割之二元关系)_#define


分四种情况讨论。

x,y都选文

那么割掉W(x,T)+W(y,T),失去的喜悦值就是bx+by+dx,y

x,y都选理

那么割掉W(S,x)+W(S,y),失去喜悦值ax+ay+cx,y

x选文,y选理

割掉W(x,T)+W(S,y)+W(x,y),失去喜悦值bx+ay+cx,y+dx,y

x选理,y选文

割掉W(S,x)+W(y,T)+W(x,y),失去喜悦值ax+by+cx,y+dx,y

写在一起
W(x,T)+W(y,T)=bx+by+dx,y
W(S,x)+W(S,y)=ax+ay+cx,y
W(x,T)+W(S,y)+W(x,y)=bx+ay+cx,y+dx,y
W(S,x)+W(y,T)+W(x,y)=ax+by+cx,y+dx,y

明显看出
W(S,x)=ax+cx,y2
W(S,y)=ay+cx,y2
W(x,T)=bx+dx,y2
W(y,T)=by+dx,y2
W(x,y)=cx,y+dx,y2

那么对于多个,我们推广。
W(S,x)=ax+cx,x所有朋友2
W(x,T)=bx+dx,x所有朋友2
W(x,y)=cx,y+dx,y2

然后根据最大流=最小割,跑一遍dinic或者GAP就好了

PS:因为可能会有小数,所以可以把边权乘个2,最后再除回来

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define N 10005
#define M 105
using namespace std;
int n,m,m1,n1,sum,zs[20*N],f[20*N],fs[N],dat[N],nt[20*N],dt[20*N],h[N],a[M][M],b[M][M],c[M][M],d[M][M],w[M][M],l[M][M],s1[M][M],s2[M][M],fx[4][2]={{1,0},{0,1},{0,-1},{-1,0}};
void link(int x,int y,int p)
{
nt[++m1]=fs[x];
fs[x]=m1;
dt[m1]=y;
f[m1]=p;
}
void make(int x,int y,int v1,int v2)
{
link(x,y,v1);
link(y,x,v2);
zs[m1]=m1-1;
zs[m1-1]=m1;
}
bool bfs()
{
memset(h,0,sizeof(h));
h[1]=dat[1]=1;
int i=0,j=1,k,p;
while(i++<j)
{
for(k=fs[dat[i]];k;k=nt[k])
{
p=dt[k];
if (f[k]>0&&h[p]==0) h[p]=h[dat[i]]+1,dat[++j]=p;
}
}
return h[n1]>0;
}
int dinic(int k,int s)
{
if (k==n1) return s;
int i,p,l,sl=0;
for(i=fs[k];i;i=nt[i])
{
p=dt[i];
if (f[i]>0&&h[p]==h[k]+1)
{
l=dinic(p,min(s,f[i]));
if (l>0)
{
f[i]-=l;
f[zs[i]]+=l;
s-=l;
sl+=l;
if (s==0) break;
}
}
}
if (sl==0) h[k]=-1;
return sl;
}
int main()
{
cin>>n>>m;
sum=0;
n1=n*m+2;
m1=0;
int p,i,j,k;
fo(i,1,n) fo(j,1,m) scanf("%d",&a[i][j]),sum+=a[i][j];
fo(i,1,n) fo(j,1,m) scanf("%d",&b[i][j]),sum+=b[i][j];
fo(i,1,n-1) fo(j,1,m) scanf("%d",&c[i][j]),sum+=c[i][j],s1[i][j]+=c[i][j],s1[i+1][j]+=c[i][j];
fo(i,1,n-1) fo(j,1,m) scanf("%d",&d[i][j]),sum+=d[i][j],s2[i][j]+=d[i][j],s2[i+1][j]+=d[i][j];
fo(i,1,n) fo(j,1,m-1) scanf("%d",&w[i][j]),sum+=w[i][j],s1[i][j]+=w[i][j],s1[i][j+1]+=w[i][j];
fo(i,1,n) fo(j,1,m-1) scanf("%d",&l[i][j]),sum+=l[i][j],s2[i][j]+=l[i][j],s2[i][j+1]+=l[i][j];
fo(i,1,n)
{
fo(j,1,m)
{
int v=(i-1)*m+j+1;
make(1,v,2*a[i][j]+s1[i][j],0);
make(v,n1,2*b[i][j]+s2[i][j],0);
fo(k,0,3)
{
int x=i+fx[k][0],y=j+fx[k][1];
int v1=(x-1)*m+y+1;
if (x>0&&y>0&&x<=n&&y<=m&&v1>v)
{
int g;
if(k==0) g=c[i][j]+d[i][j];
if(k==1) g=w[i][j]+l[i][j];
if(k==2) g=w[x][y]+l[x][y];
if(k==3) g=c[x][y]+d[x][y];
make(v,v1,g,g);
}
}
}
}
int ans=0;
while(bfs()) ans+=dinic(1,200000000);
cout<<sum-ans/2;
}