Luogu P4313 文理分科
最小割
這道題運用了最小割最常用的一種用法:集合劃分。
因為源匯最小割即就是將源匯劃分到不同的集合,那麼最簡單的應用就是最小代價劃分集合了。
本題中,題意是將 \(n\cdot m\) 個學生劃分文理科,每人只能選一科且選不同的科有不同的收益,求最大收益,符合集合劃分的條件,就理所當然地想到了最小割。
至於求最大收益,不妨就先將所有收益加起來,再減去最小代價(即最小割),便是最大收益了。
但是本題的難點在於,如果相鄰同學選一樣的(以下稱為一個組合),還會有額外收益。
於是我們需要加一點限制,使得我們在最後求最小割的時候,對於每一個組合:要麼滿足組合內的所有成員,都在同一個子集(包含源點的子集 \(S\)
於是大致見圖思路出來了:
-
對於每一個點(每一位同學)\(i\):
連 \(s\rightarrow i\) 容量為 \(art_i\);
連 \(i\rightarrow t\) 容量為 \(science_i\)。
-
對於每一個組合 \(i\),新建兩個點 \(x_i,y_i\):
連 \(s\rightarrow x_i\) 容量為 \(same\_art_i\);
連 \(y_i\rightarrow t\) 容量為 \(same\_science_i\)
對於該組合內的每個點(即該點+上下左右四個點)\(j\in i\):
連 \(x_i\rightarrow j\) 容量為 \(+\infty\);
連 \(j\rightarrow y_i\) 容量為 \(+\infty\)。
這裡解釋一下這麼連的原因:
如果要 \(s\rightarrow x_i\) 這條邊(即要這個組合所有同學都選文科的收益),那麼就不割這條邊。但是,又因為 \(x_i\) 向這個組合內每個點都連了一條 \(+\infty\) 的邊,所以這些邊便不會被割掉。那麼為了防止 \(s\) 與 \(t\) 聯通,自然就會割掉這個組合內每個點與 \(t\) 連的邊(即都不選理科)。反之都選理科亦然。
如果放棄這個組合(即這個組合內每個成員選的科不都一樣),那麼就會割掉 \(s\rightarrow x_i\) 和 \(y_i\rightarrow t\) 這兩條邊,那麼就相當於這個組合內的每個點都互相獨立了,可以任意選科。
注:不能將 \(x_i\) 與 \(y_i\) 合併成一個點來連邊,這樣會使上述放棄組合的情況無法達到(即無法破壞組合獨立選擇)。
Code
#include<bits/stdc++.h>
//#define int long long
#define pair pair<int,int>
using namespace std;
inline void end()
{
puts("");
system("pause");
}
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
const int N=3e4+4,M=5e5+5;
int n,m,nm,s,t,ans,Maxflow;
int first[N],nex[M],to[M],w[M],num=1;
inline void add(int u,int v,int val)
{
nex[++num]=first[u];
first[u]=num;
to[num]=v;
w[num]=val;
}
inline void Add(int u,int v,int val)
{
add(u,v,val);
add(v,u,0);
}
namespace ISAP
{
int dep[N],gap[N],cur[N];
void bfs()
{
memset(dep,-1,sizeof(dep));
memset(gap,0,sizeof(gap));
queue<int> q;
q.push(t);
dep[t]=0;gap[0]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=first[u];i;i=nex[i])
{
int v=to[i];
if(dep[v]!=-1) continue;
dep[v]=dep[u]+1;
gap[dep[v]]++;
q.push(v);
}
}
}
inline int dfs(int u,int in)
{
if(u==t) return in;
int out=0;
for(int i=cur[u];i;i=nex[i])
{
cur[u]=i;
int v=to[i];
if(!w[i]||dep[v]!=dep[u]-1) continue;
int res=dfs(v,min(w[i],in-out));
w[i]-=res;
w[i^1]+=res;
out+=res;
if(in==out) return out;
}
gap[dep[u]]--;
if(!gap[dep[u]]) dep[s]=3*nm+3;
dep[u]++;
gap[dep[u]]++;
return out;
}
void work()
{
bfs();
while(dep[s]<3*nm+2)
{
memcpy(cur,first,sizeof(first));
Maxflow+=dfs(s,1e9);
}
}
}
inline int id(int i,int j){return (i-1)*m+j;}
int dx[5]={-1,0,1,0,0},dy[5]={0,-1,0,1,0};
int main()
{
//1|nm|nm|nm|1
//源點|每個組合"選文"|每個座位|每個組合"選理"|匯點
n=read(),m=read(),nm=n*m;
s=0,t=3*nm+1;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
int val=read();ans+=val;
Add(s,id(i,j)+nm,val);//s -> i
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
int val=read();ans+=val;
Add(id(i,j)+nm,t,val);//i -> t
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
int val=read();ans+=val;
Add(s,id(i,j),val);//s -> x_i
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
int val=read();ans+=val;
Add(id(i,j)+2*nm,t,val);//y_i -> t
for(int k=0;k<5;++k)
{
int x=i+dx[k],y=j+dy[k];
if(x<1||y<1||x>n||y>m) continue;
Add(id(i,j),id(x,y)+nm,1e9);//x_i -> j
Add(id(x,y)+nm,id(i,j)+2*nm,1e9);//j -> y_i
}
}
}
ISAP::work();
printf("%d",ans-Maxflow);
end();
return 0;
}