BZOJ1412: [ZJOI2009]狼和羊的故事

题目描述:

Description

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

Output
文件中仅包含一个整数ans,代表篱笆的最短长度。
Sample Input
2 2
2 2
1 1

Sample Output
2

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

题解:

(ps:好久没写blog了。。。
这题显然用网络流来解决。
最小割=最大流
用最少的栏杆将羊和狼隔开,可以转化成最小割的模型。
建立超级源S和超级汇T。将超级源与狼建边,流量为inf。将羊与超级汇建边,流量为inf。将狼与羊,空地与空地,空地与羊,狼与空地建边,流量为1。那么刷一次最小割(即刷DINIC)就是答案了。因为这样刷相当于找建围栏的最好方法。。。
代码如下:

#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int maxm=105,maxn=500005,inf=1<<30,flg[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
int n,n1,m1,S,T,tot,ans,map[maxm][maxm],lnk[maxn],nxt[maxn],son[maxn],dep[maxn],w[maxn],que[maxn],cur[maxn];
inline int read(){
    int x=0; char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
    return x;
}
bool bfs(){
    int head=0,tail=1; que[tail]=S;
    memset(dep,255,sizeof(dep)); dep[S]=0;
    while (head<tail) {
        head++;
        for (int j=lnk[que[head]];j!=-1;j=nxt[j])
        if (dep[son[j]]==-1&&w[j]>0) {
            dep[son[j]]=dep[que[head]]+1; que[++tail]=son[j];
        }
    }
    if (dep[T]==-1) return 0; else return 1;
}
int dfs(int x,int flow) {
    if (x==T) return flow;
    for (int& j=cur[x];j!=-1;j=nxt[j])
    if (dep[son[j]]==dep[x]+1&&w[j]!=0){
        int x=dfs(son[j],min(flow,w[j]));
        if (x) {w[j]-=x; w[j^1]+=x; return x;}
    }
    return 0;
}
int dinic(){
    int sum; ans=0;
    while (bfs()) {
        for (int i=1;i<=T;i++) cur[i]=lnk[i];
        while (sum=dfs(S,inf)) ans+=sum;
    }
    return ans;
}
void add(int x,int y,int z) {
    son[++tot]=y,w[tot]=z,nxt[tot]=lnk[x],lnk[x]=tot;
    son[++tot]=x,w[tot]=0,nxt[tot]=lnk[y],lnk[y]=tot;
}
int main(){
    n1=read(),m1=read(); n=n1*m1; S=++n,T=++n; tot=-1;
    memset(lnk,255,sizeof(lnk));
    memset(nxt,255,sizeof(nxt));
    for (int i=1;i<=n1;i++)
    for (int j=1;j<=m1;j++){
        map[i][j]=read();
        if (map[i][j]==1) add(S,(i-1)*m1+j,inf);
        else if (map[i][j]==2) add((i-1)*m1+j,T,inf);   
    }
    for (int i=1;i<=n1;i++)
    for (int j=1;j<=m1;j++)
    for (int k=0;k<4;k++) {
        int x=i+flg[k][0],y=j+flg[k][1];
        if (x<1||x>n1||y<1||y>m1||map[i][j]==2) continue;
        if (map[i][j]==1&&map[x][y]==1) continue;
        add((i-1)*m1+j,(x-1)*m1+y,1);
    }
    printf("%d\n",dinic());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值