hdu 3081 Marriage Match II

    这道题的大意就是给出一些关系,可以确定某些男孩是女孩的喜欢的那个...在每一轮中,女孩可以选择一个之前选择中没有选择过的男孩作为她的男朋友.

    这道题的解法有两种,一种是二分枚举轮数来跑最大流,如果能跑满流,则证明,可以进行这么多轮.第二种是进行二分匹配,把所有的边加好,然后进行二分匹配,如果能完全匹配,则删去匹配的这些边,然后继续匹配.

    这里就讲第一种解法.至于为什么会写这篇题解,这道题的建图刚开始一下子就想到了,把每个有关系男孩和女孩之间加一条权值是1的边,在源点和女生之间加一条轮数K,每个男孩和终点加一条轮数K.这样子如果能跑满流,证明为每个男孩和女孩都找到了,K个不同的伴侣.

    至于,这么建跑出来的那些边,必定可以让每个女生选择自己的不同的男生刚好选择K次嘛,这里感觉很有点迷惑,所以写了题解.希望大牛指点一下,为什么会想到这么建图,原因可能是,如果此时K=1,这样就变成了一个最大二分匹配,这是可以很快想到恰好能匹配1次,(额,显然的),自然就推理到如果如果男女和源汇之间建成K时的时候,跑出来的最大流自然是可以满足这些女生恰好挑选各自不同的男生组成K轮.其次,如果恰好组成K轮选择的那些匹配也是使得男女的各自容量都是K.

    就是说,如果这些选出的匹配要能满足条件,必须男女容量是K,但满足男女容量是K的那些挑选出来的边,是否能恰好组成不同的匹配K次.

    这道题的建图与hdu3488 Tour 的建图可以说是一样的,,那道题的模型就是要在一张给定边的有向图上选择一些权值最小边集组成环恰好覆盖所有顶点,那道题不同的就是,所有的中间容量都是1,而这道题是K,既然是这样,可不可以把这道3081 理解成在一张图上去用环覆盖K次,肯定是可以的,只不过有一点小小的差别,就是这道题可能某个顶点指向自己,只需要把单个顶点的也认为是一个环就可以了.这样这道题就抽象成了一个有向图覆盖K次,在这里可以再次联想到有向图的欧拉回路.如果一个有向图每个顶点的入度和出度相等那么有向图就存在欧拉回路,这里现在存在,在这道题的这张图中不一定是联通图,没有关系,对每一个连通分量了来说,这个连通分量,可以用这下边集一笔画成,每个顶点遍历K次,那么就转化成了,要总共遍历K次顶点,那么必须先遍历完了一遍顶点集之后,再一遍再一遍...这个的证明问了一下风神,他觉得貌似是可以的.也许大概是可以的吧.

    这道题就这样简单说了一下.网络流的建图要求对网络流各种性质的熟悉.要多做题,多总结,加油!

    顺便贴个代码吧

 

#include<stdio.h>
#include<memory.h>
#define maxn 240
#define maxe 100000
#define INF 0x7fffffff

int qul[110][110],set[110];
int nn,m,fr;

int n,s,t;
int size,net[maxn];
int gap[maxn];//gap优化 
int dist[maxn];//距离标号 
int pre[maxn];//前驱 
int curedge[maxn];//当前弧 

struct node
{
    int v,next;
    int cap;
    int flow;
};
struct node edge[maxe];

void inti()
{
    size=0;
    memset(net,-1,sizeof(net));
}

void chu()
{
    int i,f;
    for(i=1;i<=nn;i++)
    {
        set[i]=i;
        for(f=1;f<=nn;f++)
        {
            qul[i][f]=1;
        }
    }
}

void add(int u,int v,int cap)
{
    edge[size].v = v;
    edge[size].cap = cap;
    edge[size].flow = 0;
    edge[size].next = net[u];
    net[u] = size;
    ++size;

    edge[size].v = u;
    edge[size].cap = 0;
    edge[size].flow = 0;
    edge[size].next = net[v];
    net[v] = size;
    ++size;
}

int isap()
{
    int curflow,u,temp,neck,i;
    int maxflow;
    memset(gap,0,sizeof(gap));
    memset(pre,-1,sizeof(pre));
    memset(dist,0,sizeof(dist));
    for(i=1;i<=n;i++) 
    {
        curedge[i]=net[i];//将当前弧初始话成邻接表的第一条边 
    }
    gap[0]=n;    //gap[nv]=nv; 错
    maxflow=0;
    u=s;
    while(dist[s] < n)
    {
        if(u==t)
        {        //找到一条增广路 
            curflow=INF;
            for(i=s;i!=t;i=edge[curedge[i]].v)
            {    //沿着增广路找到最小增广流量 
                if(curflow > edge[curedge[i]].cap)
                {
                    neck=i;
                    curflow=edge[curedge[i]].cap;
                }
            }
            for(i=s;i!=t;i=edge[curedge[i]].v)
            {    //更新 
                temp=curedge[i];
                edge[temp].cap-=curflow;
                edge[temp].flow+=curflow;
                temp^=1;
                edge[temp].cap+=curflow;
                edge[temp].flow-=curflow;
            }
            maxflow+=curflow;
            u=neck;//下次直接从关键边的u开始新一轮的增广 
        }
        for(i=curedge[u];i!=-1;i=edge[i].next)//找到一条允许弧 
        {
            if(edge[i].cap>0 && dist[u]==dist[edge[i].v]+1)
            {
                break;
            }
        }
        if(i!=-1)    //如果找到 将u指向v 
        {
            curedge[u]=i;
            pre[edge[i].v]=u;
            u=edge[i].v;
        }
        else
        {        //找不到 
            if(0 == --gap[dist[u]]) 
            {
                break;//出现断层
            }
            curedge[u] = net[u];//把当前弧重新设为邻接表中满足要求的第一条弧
            for(temp=n,i=net[u];i!=-1;i=edge[i].next)
            {
                if(edge[i].cap > 0)
                {
                    temp=temp<dist[edge[i].v]?temp:dist[edge[i].v];
                }
            }
            dist[u]=temp+1;//将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最小值加1
            gap[dist[u]]++;
            if(u != s)
            {    
                u=pre[u];
            }
        }
    }
    return maxflow;
}

void merge(int a,int b)
{
    set[a]=b;
}

int find(int x)
{
    return x==set[x]?x:set[x]=find(set[x]); 
}

void fun()
{
    int i,f;
    for(i=1;i<=nn;i++)
    {
        for(f=1;f<=nn;f++)
        {
            qul[set[i]][f]&=qul[i][f];            
        }
    }
}

void build(int mid)
{
    int i,f;
    inti();
    n=nn*2+2;
    s=nn*2+1;
    t=nn*2+2;
    for(i=1;i<=nn;i++)
    {
        add(s,i,mid);
        add(i+nn,t,mid);
        for(f=1;f<=nn;f++)
        {
            if(0 == qul[set[i]][f])
            {
                add(i,nn+f,1);
            }
        }
    }
}

int bs()
{
    int l,r,mid;
    l=0;
    r=nn+1;
    while(l != r-1)
    {
        mid=(l+r)>>1;
        build(mid);
        if(nn*mid == isap())
        {
            l=mid;    
        }
        else
        {
            r=mid;
        }        
    }
    return l;
}

int main()
{
    int a,b,i,f,num;
    scanf("%d",&num);
    for(i=0;i<num;i++)
    {
        scanf("%d%d%d",&nn,&m,&fr);
        chu();
        for(f=0;f<m;f++)
        {
            scanf("%d%d",&a,&b);
            qul[a][b]=0;
        }        
        for(f=0;f<fr;f++)
        {
            scanf("%d%d",&a,&b);
            merge(find(a),find(b));    
        }
        fun();
        printf("%d\n",bs());
    }
    return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值