问题描述
LG2598
BZOJ1412
题解
看到要把狼和羊两个物种分开
自然想到最小割。
发现\((x,y)\)可以向上下左右走以获得贡献,所以建边:\((x,y),(x-1,y)\),\((x,y),(x,y-1)\),\((x,y),(x,y+1)\),\((x,y),(x+1,y)\)(要在矩阵内)
这些边的边权为\(1\),代表在这里建立栅栏(割断边)要\(1\)的代价
然后从源点向狼,羊向汇点建边,边权为\(INF\),代表不可割断。
\(\mathrm{Code}\)
#include
using namespace std;
template
void read(Tp &x){
x=0;char ch=1;int fh;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-'){
fh=-1;ch=getchar();
}
else fh=1;
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
x*=fh;
}
const int maxn=107;
const int INF=0x3f3f3f3f;
int n,m;
int a[maxn][maxn];
int S,T,d[maxn*maxn];
int Head[maxn*maxn],Next[1000007],v[1000007],tot=1,w[1000007];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int Iter[maxn*maxn];
void add(int x,int y,int z){
v[++tot]=y,Next[tot]=Head[x],Head[x]=tot,w[tot]=z;
}
int calc(int x,int y){//错误笔记:calc写错
return (x-1)*m+y;
}
bool check(int x,int y){
if(x>n||x<1||y>m||y<1) return false;
return true;
}
int ans;
bool bfs(){
memset(d,0,sizeof(d));
queueq;q.push(S);d[S]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=Head[x];i;i=Next[i]){
if(d[v[i]]||!w[i]) continue;
q.push(v[i]);d[v[i]]=d[x]+1;
if(v[i]==T) return true;
}
}
return false;
}
int dfs(int x,int flow){
if(x==T) return flow;
int rest=flow;
for(int i=Head[x];i&&rest;i=Next[i]){
if(d[v[i]]!=d[x]+1||!w[i]) continue;
int k=dfs(v[i],min(rest,w[i]));
if(!k) d[v[i]]=0;
else{
w[i]-=k,w[i xor 1]+=k;
rest-=k;
}
}
return flow-rest;
}
int main(){
read(n);read(m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
read(a[i][j]);
}
}
S=n*m+1,T=S+1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int p=calc(i,j);
if(a[i][j]==1){
add(S,p,INF);add(p,S,0);
}
else if(a[i][j]==2){
add(p,T,INF);add(T,p,0);
}
for(int k=0;k<4;k++){
int xx=i+dx[k],yy=j+dy[k];
if(check(xx,yy)){
add(p,calc(xx,yy),1),add(calc(xx,yy),p,0);
}
}
}
}
while(bfs()){
int t;
for(int i=1;i<=n*m;i++) Iter[i]=Head[i];
while(t=dfs(S,0x3f3f3f3f)) ans+=t;
}
printf("%d\n",ans);
return 0;
}