UVA 11419 SAM I AM 网络流

今天是在家待的最后一天了……在家的15天一直没有写过博客,今天还是写一篇吧。

思路:

这道题我用的是网络流。(好吧其实是因为不会KM算法)
用网络流做二分图的基本定理:
最大点权独立=总点权-最小点权覆盖
最大点权独立集:点和点之间没有边相连接
最小点权覆盖集:每一条边都有一个点被点亮,所有点的点权之和最小
这道题可转换为求最小点权覆盖集,也就是求最小割

怎么建图是我看了别人的,二分图的X集对应行号,Y集对应列号,把每个敌人的行号和列号相连。
之后求答案的方式很简单,跑个最小割即可。但我又不知道怎么输出割的是哪几条边,所以再次看了别人的。
做法详见代码。
以下是关于此做法(get_ans)的感性认知:
如果是这样
——1—
S—-2—(123汇集于5)5—-T
——3—
(假设S—1边满流)
很明显我们要割5—T这条边。在get_ans时我们可以通过一条未满流的边到达5,因此2和3都会被访问。
那么1如何被访问呢?
当访问到5时 1->5的反向边cap=0 flow>0 满足条件,返回访问1。
如果是这样
—–2—-
S—1 —–3—-(2 3 4汇集与T)
—–4—-
那么1肯定满流 访问不了,因此2 3 4也访问不了 。

注意:

不能再用w[i]的写法了,应用cap[i]和flow[i]
注意flow[i]和w[i]的加减变化刚好相反,这点也比较容易理解
flow流量 既然流过了 自然要加 反向边当然要减
而w可理解为还有多少流量可以走 流过自然要减

#include<cstdio>
#include<cstring>
#include<queue>
#define INF 10000000
using namespace std;
const int P=2500,E=2008106; 
int r,c,n;
int T,S;
int to[E],nxt[E],cap[E],flow[E],head[P],etot;
int dis[P];
bool vis[P],foot[P];
void adde(int u,int v,int c)
{
    to[++etot]=v;
    nxt[etot]=head[u];
    cap[etot]=c;
    head[u]=etot;
}
void ins(int u,int v,int c)
{
    adde(u,v,c);
    adde(v,u,0);
}
bool bfs()
{
    memset(dis,-1,sizeof(dis));
    queue<int> q;
    q.push(S);
    dis[0]=0;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=nxt[i]){
            int v=to[i];
            if(cap[i]-flow[i]>0&&dis[v]==-1){
                dis[v]=dis[u]+1;
                q.push(v);
            }
        }
    }
    return dis[T]==-1?0:1;
}
int dfs(int u,int a)
{
    if(u==T||a==0) return a;
    int used=0,f;
    for(int i=head[u];i;i=nxt[i]){
        int v=to[i];
        if(dis[v]==dis[u]+1&&cap[i]-flow[i]>0){
            f=dfs(v,min(a-used,cap[i]-flow[i]));
            used+=f;
            flow[i]+=f;flow[i^1]-=f;//!!
            if(used==a) return used; 
        }
    }
    if(!used) dis[u]=-1;
    return used;
}
void dinic()
{
    int ans=0;
    while(bfs()){
        ans+=dfs(S,INF);
    } 
    printf("%d ",ans);
}
void get_ans(int u)
{
    foot[u]=1;
    for(int i=head[u];i;i=nxt[i])
        if(cap[i]-flow[i]>0&&!foot[to[i]]) 
        get_ans(to[i]);

}
void init()
{
    etot=1;
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    memset(foot,0,sizeof(foot));
    memset(cap,0,sizeof(cap));
    memset(flow,0,sizeof(flow));
}
int main()
{
    while(scanf("%d%d%d",&r,&c,&n)&&(n||c||r)){
        init();
        T=r+c+1,S=0;
        for(int i=1;i<=n;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            ins(x,r+y,INF);
            if(!vis[x]){
                ins(S,x,1);
                vis[x]=1;
            }
            if(!vis[r+y]){
                ins(r+y,T,1);
                vis[r+y]=1;
            }
        }
        dinic();
        get_ans(S);
        for(int i=1;i<=r;i++)
        if(vis[i]&&!foot[i]) printf("r%d ",i);
        for(int i=r+1;i<=r+c;i++)
        if(vis[i]&&foot[i]) printf("c%d ",i-r);
        printf("\n");
    }
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值