【ZJOI2009】狼和羊的故事

本文介绍了一个有趣的算法问题——如何通过最小割算法合理地在羊狼圈中增设篱笆以隔离狼和羊,确保它们各自留在自己的领地内。文章详细解释了问题背景及转换模型的方法,并提供了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

  “狼爱上羊啊爱的疯狂,谁让他们真爱了一场;狼爱上羊啊并不荒唐,他们说有爱就有方向......”
  Orez听到这首歌,心想:狼和羊如此和谐,为什么不尝试羊狼合养呢?说干就干!
  Orez的羊狼圈可以看作一个n*m个矩阵格子,这个矩阵的边缘已经装上了篱笆。可是Drake很快发现狼再怎么也是狼,它们总是对羊垂涎三尺,那首歌只不过是一个动人的传说而已。所以Orez决定在羊狼圈中再加入一些篱笆,还是要将羊狼分开来养。
  通过仔细观察,Orez发现狼和羊都有属于自己领地,若狼和羊们不能呆在自己的领地,那它们就会变得非常暴躁,不利于他们的成长。
  Orez想要添加篱笆的尽可能的短。当然这个篱笆首先得保证不能改变狼羊的所属领地,再就是篱笆必须修筑完整,也就是说必须修建在单位格子的边界上并且不能只修建一部分。

Input

  输入数据存放在文本文件ws.in中。
  文件的第一行包含两个整数n和m。接下来n行每行m个整数,1表示该格子属于狼的领地,2表示属于羊的领地,0表示该格子不是任何一只动物的领地。

Output

  输出数据存放在文本文件ws.out中。
  文件中仅包含一个整数ans,代表篱笆的最短长度。

Sample Input

2 2
2 2
1 1

Sample Output

2

Data Constraint

10%的数据 n,m≤3
30%的数据 n,m≤20
100%的数据 n,m≤100

题解

题目的要求就是通过加建围栏要把狼还有羊分开,
转化一下模型,就是最小割了。
每一个点与周围的点四路通,
就相当于连一条边,
如果建一个围栏,就等同于将这条边割掉。
将所有的羊与源点连一条边,
所有的狼和汇点连一条边,
点与点之间连一条边权为1的边。
最后跑最大流就行了。

code

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#include <time.h> 
#define ll long long
#define N 100003
#define M 103
#define db double
#define P putchar
#define G getchar
#define inf 998244353
using namespace std;
char ch;
void read(int &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

int nxt[N*2],to[N*2],v[N*2],last[N],cur[N],tot;
int q[N],h[N],S,T,ans;
int a[M][M],n,m,t;

bool bfs()
{
    int head=0,tail=1;
    for(int i=0;i<=T;i++)h[i]=-1;
    q[0]=S;h[S]=0;
    while(head!=tail)
    {
        int now=q[head];head++;
        for(int i=last[now];i;i=nxt[i])
            if(v[i] && h[to[i]]==-1)
            {
                h[to[i]]=h[now]+1;
                q[tail++]=to[i];
            }
    }
    return h[T]!=-1;
}

int dfs(int x,int f)
{
    if(x==T)return f;
    int w,used=0;
    for(int i=cur[x];i;i=nxt[i])
        if(h[to[i]]==h[x]+1)
        {
            w=f-used;
            w=dfs(to[i],min(w,v[i]));
            v[i]-=w;v[i^1]+=w;
            if(v[i])cur[x]=i;
            used+=w;
            if(used==f)return f;
        }
    if(!used)h[x]=-1;
    return used;
}

void dinic()
{
    while(bfs())
    {
        for(int i=0;i<=T;i++)
            cur[i]=last[i];
        ans+=dfs(S,inf);
    }
}

void ins(int x,int y,int z)
{
    nxt[++tot]=last[x];
    to[tot]=y;
    v[tot]=z;
    last[x]=tot;
}

int get(int x,int y)
{
    return x*m-m+y;
}

int main()
{
    tot=1;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++)
        {
            t=get(i,j);
            if(a[i][j]==1)ins(S,t,inf),ins(t,S,inf);
            if(a[i][j]==2)ins(T,t,inf),ins(t,T,inf);

            if(i!=n)ins(t,get(i+1,j),1),ins(get(i+1,j),t,1);
            if(j!=m)ins(t,get(i,j+1),1),ins(get(i,j+1),t,1);
        }
    dinic();
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值