[kuangbin带你飞]专题十 匹配问题-POJ-2112(二分+网络流or多重最大匹配)

Optimal Milking POJ - 2112

题意:

有k个机器(产奶)。c头牛。
每个机器最多可以接纳m头牛。
给你牛机器之间的距离。

怎样分配可以使牛都可以喝到奶,且走最远路的牛,所走的距离最短。

思路:

  1. 由于牛要走最短路,所以可以先floyd预处理。
  2. 可以二分答案。
  3. 由于问题是多重匹配,所以,这时候有两种思路。

反思

  1. 一边多重,可以跑最大匹配。
  2. 两边都是,那么就跑网络流吧。

大佬的笔记

传送门

AC1(多重匹配,只适合一个集合多重)

#include <iostream>
#include <map>
#include <string>
#include <cstring>
#define mst(x,a) memset(x,a,sizeof(x))
#define fzhead EDGE(int _v, int _next)
#define fzbody to(_v), next(_next)
using namespace std;
const int maxn=3e2+10;
const int maxm=500*500+10;
const int inf=0x3f3f3f3f;
struct node{
    int cnt,k[maxn];//cnt:右独立集匹配的个数, k右独立集,匹配点的集合
}link[maxn];
struct EDGE{
    int to,next;
    EDGE(){}
    fzhead:fzbody{}
}e[maxm];
int head[maxn],cnt,d[maxn][maxn],mx[maxn];
void add(int bg, int to){
    e[++cnt]=EDGE(to, head[bg]);
    head[bg]=cnt;
}
int k,c ,m,vis[maxn],p;
int x[maxn],y[maxn];
bool used[maxn];
bool dfs(int u)
{
    for(int i=head[u]; i!=-1; i=e[i].next)
    {
        int v=e[i].to;
        if(!used[v])
        {
            used[v] = 1;
            if(link[v].cnt<mx[v]){///
                link[v].k[link[v].cnt++]=u;
                return 1;
            }///
            for(int j=0; j<link[v].cnt; j++)
            if(dfs(link[v].k[j]))
            {
                ///vis[v]=u;//cx[u] = v ,cy[v] =  u ;
                ///vis[u]=v;
                link[v].k[j]=u;
                return 1;
            }
        }
    }
    return 0;
}
void floyd(){
    for(int K=1; K<=c+k; K++){
        for(int i=1; i<=c+k; i++){
            for(int j=1; j<=c+k; j++)d[i][j]=min(d[i][j],d[i][K]+d[K][j]);
        }
    }
}
int cntx=0;
bool check(int x)//max_match()
{
    cnt=0;
    mst(head,-1);mst(used,0);mst(link,0);
    mst(vis,0);
    int ans = 0;
    for(int i=1; i<=k; i++){
        for(int j=k+1;j<=c+k; j++){
            if(d[i][j]<=x){add(j,i);
                //cout<<"(i,j)-> "<<j<<' '<<i<<' ';
            }
        }
    }
   // cout<<endl;
    for(int i = 1;i <=k+c;++i)//if(!vis[i])
    {
        mst(used,0);
        if(!vis[i])ans += dfs(i);//最大匹配
    }
  // cout<<ans<<' '<<x<<endl;
    return ans==c;
    //printf("%d\n",p-ans);
}
void work(){
    for(int i=1; i<=c+k; i++){
        for(int j=1; j<=c+k; j++){
            cin>>d[i][j];
            if(d[i][j]==0)d[i][j]=inf;
        }
    }
    for(int i=1; i<=c+k; i++){
        if(i<=k)mx[i]=m;
        else mx[i]=1;
    }
    floyd();
    int l=0,r=1e5+10,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>k>>c>>m){
        work();
    }
    return 0;
}

AC2(网络流)

#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#include <cstdio>
//#include <>
#include <algorithm>
//#include <>
#define sz(a) ((int)a.size())
#define pb push_back
#define For(i,x,y) for(int i=(x); i<=(y); i++)
#define eb emplace_back
#define mst(x,a) memset(x,a,sizeof(x))
#define fzhead EDGE(int u, int v, int c, int f)
#define fzbody from(u), to(v), cap(c),flow(f)
using namespace std;
const int maxn=500;
const int M=0;
const int inf=0x3f3f3f3f;
struct EDGE{
    int from,to,cap,flow;
    EDGE(){}
    fzhead:fzbody{}
};
struct Dinic{
    int n,m,s,t;
    vector<EDGE> edges;
    vector<int> g[maxn];
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
///cur就是记录当前循环到了哪一条边。
    void init(int n){
        this->n = n;
        for(int i=0; i<n; i++)g[i].clear();
        edges.clear();
    }
    void add(int from, int to, int cap){
       // edges.eb(from,to,cap,0);
        //edges.eb(to,from,0,0);
        edges.pb({from,to,cap,0});
        edges.pb({to,from,0,0});
        ///加边同时,加正向和反向。
        m=edges.size();
        g[from].pb(m-2);
        g[to].pb(m-1);
    }
    bool bfs(){
        mst(vis,0);
        mst(d,0);
        queue<int>q;
        q.push(s);
        d[s]=0;
        vis[s]=1;///源点深度为1.
        while(!q.empty())
        {
            int x=q.front();q.pop();
            for(int i=0; i<sz(g[x]); i++)
            {
                EDGE&e=edges[g[x][i]];
                if(!vis[e.to]&&e.cap>e.flow){
                    vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    q.push(e.to);
                }
            }
        }
        return vis[t];///当汇点的深度不存在时,说明不存在分层图。
    }
    int dfs(int x, int a){
        ///x为当前节点,a为流量。
        if(x==t || a==0)return a;///当已经到达汇点,或者流量不存在时,返回。
        int flow=0,f;
        for(int &i=cur[x]; i<sz(g[x]); i++){
///注意这里的“&”符号,这样i增加同时也能改变cur【x】的值,达到弧优化的目的。
            EDGE& e=edges[g[x][i]];
            if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0){///判断并且增广。
                e.flow+=f;///加减
                edges[g[x][i]^1].flow-=f;
                flow+=f;
                a-=f;
                if(a==0)break;
            }
        }
        return flow;
    }
    int maxflow(int s, int t){
        this->s=s,this->t=t;
        int flow=0;
        while(bfs()){
///每一次建立分层图后,都要把cur置为每一个点的第一条边
            mst(cur,0);
        flow+=dfs(s,inf);
        }
        return flow;
    }
} dinic;
int k,c,m;
int d[maxn][maxn];
void floyd(){
    for(int K=1; K<=c+k; K++){
        for(int i=1; i<=c+k; i++){
            for(int j=1; j<=c+k; j++)d[i][j]=min(d[i][j],d[i][K]+d[K][j]);
        }
    }
}
bool check(int x){
    dinic.init(maxn);
    int s=c+k+1;
    int t=c+k+2;
    for(int i=1; i<=k; i++){
        dinic.add(s,i,m);
        for(int j=k+1; j<=c+k; j++){
            if(d[i][j]<=x)dinic.add(i,j,1);
        }
    }
    for(int j=k+1; j<=c+k; j++)dinic.add(j,t,1);
    return dinic.maxflow(s,t)==c;
}
void solve(){
    for(int i=1; i<=c+k; i++){
        for(int j=1; j<=c+k; j++){
            cin>>d[i][j];
            if(d[i][j]==0)d[i][j]=inf;
        }
    }
    floyd();
    int l=0, r=1e5+10,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>k>>c>>m){
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值