bzoj1458 士兵占领 最大流

31 篇文章 0 订阅

Description


有一个M * N的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第i行至少放置了Li个士兵, 第j列至少放置了Cj个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。

M, N <= 100, 0 <= K <= M * N

Solution


一开始以为是dp的网络流
st向每一行连边li,每一列向ed连ci,行向当前行的点连1,点向所在列连1,这样跑出来的直接是答案
这里实际上是不能拆点的,一个点要被所在的行和列算两次
感觉网络流的题目都很神奇,黄学长的建图似乎更有道理
一开始错是没有JIONG

Code


#include <stdio.h>
#include <string.h>
#include <queue>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fill(x,t) memset(x,t,sizeof(x))
#define min(x,y) ((x)<(y)?(x):(y))
const int INF=0x3f3f3f3f;
const int N=40005;
const int E=320005;
struct edge{int x,y,w,next;}e[E];
std:: queue<int> que;
int dis[N],l[N],c[N];
int rc[105][105];
int ls[N],edCnt=1;
int read() {
    int x=0,v=1; char ch=getchar();
    for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
    for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
    return x*v;
}
void addEdge(int x,int y,int w) {
    e[++edCnt]=(edge){x,y,w,ls[x]}; ls[x]=edCnt;
    e[++edCnt]=(edge){y,x,0,ls[y]}; ls[y]=edCnt;
//  printf("%d %d %d\n",x,y,w);
}
int bfs(int st,int ed) {
    while (!que.empty()) que.pop();
    que.push(st);
    fill(dis,-1);
    dis[st]=1;
    while (!que.empty()) {
        int now=que.front(); que.pop();
        for (int i=ls[now];i;i=e[i].next) {
            if (e[i].w>0&&dis[e[i].y]==-1) {
                dis[e[i].y]=dis[now]+1;
                que.push(e[i].y);
                if (e[i].y==ed) return 1;
            }
        }
    }
    return 0;
}
int find(int now,int ed,int mn) {
    if (now==ed||!mn) return mn;
    int ret=0;
    for (int i=ls[now];i;i=e[i].next) {
        if (e[i].w>0&&dis[now]+1==dis[e[i].y]) {
            int d=find(e[i].y,ed,min(mn-ret,e[i].w));
            e[i].w-=d; e[i^1].w+=d; ret+=d;
            if (ret==mn) break;
        }
    }
    return ret;
}
int dinic(int st,int ed) {
    int ret=0;
    while (bfs(st,ed)) ret+=find(st,ed,INF);
    return ret;
}
int main(void) {
    int n=read(),m=read(),k=read();
    rep(i,1,n) l[i]=read();
    rep(i,1,m) c[i]=read();
    rep(i,1,k) {
        int x=read(),y=read();
        rc[x][y]=1;
    }
    int st=0,ed=n+m+n*m+1;
    rep(i,1,n) addEdge(st,i,l[i]);
    rep(i,1,m) addEdge(i+n,ed,c[i]);
    rep(i,1,n) rep(j,1,m) {
        if (!rc[i][j]) {
            addEdge(i,(i-1)*m+j+n+m,1);
            addEdge((i-1)*m+j+n+m,j+n,1);
        } else {
            l[i]++; c[j]++;
            if (l[i]>m||c[j]>n) {
                puts("JIONG!");
                return 0;
            }
        }
    }
    printf("%d\n",dinic(st,ed));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值