=== ===
这里放传送门
=== ===
题解
非常经典的最小割建图模型啦!考虑最小的放弃的喜悦值,以S代表学文,T代表学理为例,首先对于单个同学来说,他的学文的喜悦值和学理的喜悦值不能同时存在,于是给每个同学建立一个点,从S连到它,流量为它学文的喜悦值;从它连到T,流量为它学理的喜悦值,那么这两条边必定要割掉一条。对于处理两个同学共同学文或者学理的情况,对每一对相邻的同学新建两个点,从S连到其中一个点,这个点再连到那两个同学,流量为这两个同学共同学文的喜悦值;这两个同学连到另外一个点,从这个点再连到T,流量为这两个同学共同学理的喜悦值。
搞出来就大概长这样,妈呀这图画起来真费事= =
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inc(x)(x=(x%60000)+1)
#define inf 1000000000
using namespace std;
int n,m,p[60010],tot,S,T,N,cnt,cur[60010],gap[60010],d[60010],pre[60010],sum;
struct edge{
int to,flw,nxt;
}e[3000010];
int cal(int i,int j){
return (i-1)*m+j;
}
void add(int from,int to,int flow){
e[tot].to=to;
e[tot].flw=flow;
e[tot].nxt=p[from];
p[from]=tot;
++tot;
}
void Bfs(int N){
int q[60010],head,tail;
for (int i=0;i<=N;i++)
d[i]=N+1;
memset(q,0,sizeof(q));
head=0;tail=1;
q[tail]=T;
d[T]=0;
while (head!=tail){
int u;
inc(head);
u=q[head];
for (int i=p[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
if ((e[i^1].flw>0)&&(d[v]==N+1)){
d[v]=d[u]+1;
inc(tail);q[tail]=v;
}
}
}
}
int track(){
int Min=inf;
for (int i=T;i!=S;i=e[pre[i]^1].to)
Min=min(Min,e[pre[i]].flw);
for (int i=T;i!=S;i=e[pre[i]^1].to){
e[pre[i]].flw-=Min;
e[pre[i]^1].flw+=Min;
}
return Min;
}
int ISAP(int N){
int u=S,Flow=0;
bool flag=false;
Bfs(N);
for (int i=0;i<=N;i++)
cur[i]=p[i];
memset(gap,0,sizeof(gap));
memset(pre,0,sizeof(pre));
for (int i=0;i<=N;i++)
++gap[d[i]];
while (d[S]<N){
if (u==T){
Flow+=track();
u=S;
}
flag=false;
for (int i=cur[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
if ((e[i].flw>0)&&(d[v]+1==d[u])){
flag=true;
pre[v]=i;
cur[u]=i;//更新当前弧
u=v;
break;
}
}
if (flag==false){
int v=N-1;
for (int i=p[u];i!=-1;i=e[i].nxt)
if (e[i].flw>0)
v=min(v,d[e[i].to]);
--gap[d[u]];
if (gap[d[u]]==0) break;
d[u]=v+1;
++gap[d[u]];
cur[u]=p[u];//初始化这个点对应的当前弧
if (u!=S)
u=e[pre[u]^1].to;
}
}
return Flow;
}
int main()
{
memset(p,-1,sizeof(p));
scanf("%d%d",&n,&m);
N=n*m;tot=cnt=0;
S=0;T=N*5+1;
sum=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
int x;
++cnt;
scanf("%d",&x);
sum+=x;//计算总的喜悦值
add(S,cnt,x);add(cnt,S,0);//建立每个同学对于学文的喜悦值
}
cnt=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
int x;
++cnt;
scanf("%d",&x);
sum+=x;
add(cnt,T,x);add(T,cnt,0);//建立每个同学对于学理的喜悦值
}
for (int i=1;i<n;i++)
for (int j=1;j<=m;j++){
int x,pt;
cnt=cal(i,j);
pt=cnt+N;//计算第一组点的编号
scanf("%d",&x);
sum+=x;
add(S,pt,x);add(pt,S,0);
add(pt,cnt,x);add(cnt,pt,0);
add(pt,cnt+m,x);add(cnt+m,pt,0);
}
for (int i=1;i<n;i++)
for (int j=1;j<=m;j++){
int x,pt;
cnt=cal(i,j);
pt=cnt+2*N;
scanf("%d",&x);
sum+=x;
add(pt,T,x);add(T,pt,0);
add(cnt,pt,x);add(pt,cnt,0);
add(cnt+m,pt,x);add(pt,cnt+m,0);
}
for (int i=1;i<=n;i++)
for (int j=1;j<m;j++){
int x,pt;
cnt=cal(i,j);
pt=cnt+3*N;
scanf("%d",&x);
sum+=x;
add(S,pt,x);add(pt,S,0);
add(pt,cnt,x);add(cnt,pt,0);
add(pt,cnt+1,x);add(cnt+1,pt,0);
}
for (int i=1;i<=n;i++)
for (int j=1;j<m;j++){
int x,pt;
cnt=cal(i,j);
pt=cnt+4*N;
scanf("%d",&x);
sum+=x;
add(pt,T,x);add(T,pt,0);
add(cnt,pt,x);add(pt,cnt,0);
add(cnt+1,pt,x);add(pt,cnt+1,0);
}
printf("%d\n",sum-ISAP(T));
return 0;
}
偏偏在最后出现的补充说明
做最小割这样的题的时候一定要分清楚建图是关于舍弃的价值还是得到的价值,愚蠢的ATP好几次都把这两个像和稀泥一样混到一起然后图就建成了一坨shi怎么也跑不动= =