zoj 3316 Game (一般图匹配带花树)

1.增广路:长度为奇数,第一条和最后一条边都是未匹配的边,增光路径是一条匹配边,一条未匹配边这样交错的路径,也叫做交错路

2.二分图已经得到最大匹配当且仅当没有增广路

因为如果这时候还有增广路(长度为2*k+1),其中k条匹配的边,k+1条未匹配的边,其中第一条和最后一条是未匹配的边,我们可以把原来是未匹配的(k+1)变成匹配的,原来匹配的(k)变成未匹配的,结果增加1

所以二分图的匈牙利算法枚举左边的点集,每次若找到增广路,匹配数就+1

该原理是匈牙利算法的核心

3.寻找增广路的做法是:从一个还没被匹配的点(exposed vertex)出发,中间形成交错路径,最后停止在一个没被匹配的点,这就是一条增广路,

定义:在路径上给这些点从1开始标号,奇数的点我们称为外点,偶数的点我们称为内点,可以发现他们恰好对应两个集合(X为外点的集合,Y为内点的集合)

直接寻找增广路做法不适用于一般图匹配的原因:寻找增广路时会形成环,导致有些点既是内点又是外点

那又怎样呢?

http://hi.baidu.com/cloudygoose/item/1ccf42a1c678d0d85af19178
一些题解的说法是二分图中会形成环,但环的边是偶数的,不影响,而在一般图中形成的环是奇数的

这样说是为了解释一般图为什么不合适,实际上在二分图的操作中是不会出现环的,如果出现,说明开始搜索的起点不是一个未匹配的点或者原来的匹配方式有错误(存在一个点存在于两个匹配中)

二分图的特殊性决定了寻找增广路的过程中,外点、内点、外点、内点……这样的分布能对应X集合、Y集合、X集合、Y集合……

但一般图中存在从X集合到X集合的边和从Y集合到Y集合的边,若直接寻找增广路会有些点既是内点又是外点,如下图,v既是内点又是外点,所以才会有缩点等一系列操作,都是为了缩点后能继续用增广路的做法

你会问,既是外点又是内点又怎样呢?

找到增广路时都会把未匹配边变成匹配边,匹配边变成未匹配边,若是有些点既是外点又是内点,会匹配出错,即一个点存在于两个匹配中

**值得注意的是,一般图中若出现环只可能是如下图的情形,即root连2条未匹配边,因为寻找增广路是从一个未匹配点出发的

File:Edmonds blossom.svg
4.带花树的做法是:

像匈牙利算法那样不断枚举点寻找增广路,

当找到环时,找到u和v的最近公共祖先,

分别从u和v跑到最近公共祖先的过程中,把环里面的边从有向变成无向的,并环中点的所在集合都设为root

有N个棋子在棋盘上,2个人轮流拿走一个棋子,第一步可以拿任意一个,而之后每一步必须拿上一步拿走的棋子曼哈顿长度L以内的棋子,问,后手是否能赢

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 402
int base[N],pre[N],match[N],que[N],inque[N],inpath[N],inblossom[N],front,rear;
int n,x[N],y[N],L,g[N][N];

int findancestor(int u,int v)
{
    memset(inpath,0,sizeof(inpath));
    while(1)
    {
        u=base[u];
        inpath[u]=1;
        if(match[u]==-1) break;
        u=pre[match[u]];
    }
    while(1)
    {
        v=base[v];
        if(inpath[v]) return v;
        v=pre[match[v]];
    }
}
void reset_trace(int u,int anc)
{
    while(u!=anc)
    {
        int v=match[u];
        inblossom[base[u]]=1;
        inblossom[base[v]]=1;
        v=pre[v];
        if(base[v]!=anc) pre[v]=match[u];
        u=v;
    }
}
void contract(int u,int v)
{
    int anc=findancestor(u,v);
    memset(inblossom,0,sizeof(inblossom));
    reset_trace(u,anc);
    reset_trace(v,anc);
    if(base[u]!=anc) pre[u]=v;
    if(base[v]!=anc) pre[v]=u;
    for(int i=1;i<=n;++i)
        if(inblossom[base[i]])
        {
            base[i]=anc;
            if(!inque[i])
            {
                inque[i]=1;
                que[rear++]=i;
            }
        }
}
bool bfs(int st)
{
    front=rear=0;
    for(int i=1;i<=n;++i) pre[i]=-1,inque[i]=0,base[i]=i;
    que[rear++]=st;inque[st]=1;
    while(front<rear)
    {
        int u=que[front++];
        for(int v=1;v<=n;++v)
        if(g[u][v]&&base[u]!=base[v]&&match[u]!=v)
        {
            if(v==st||(match[v]!=-1&&pre[match[v]]!=-1)) // circle
                contract(u,v);
            else if(pre[v]==-1)
            {
                pre[v]=u;
                if(match[v]!=-1)
                    que[rear++]=match[v],inque[match[v]]=1;
                else
                {
                    u=v;
                    while(u!=-1)
                    {
                        v=pre[u];
                        int w=match[v];
                        match[u]=v;match[v]=u;
                        u=w;
                    }
                    return true;
                }
            }
        }
    }
    return false;
}
int num[N],sz,vis[N],pp[N];
void dfs(int u)
{
    vis[u]=sz;num[sz]++;
    for(int i=1;i<=n;++i)
        if(g[u][i]&&!vis[i])
        dfs(i);
}
bool solve()
{
    for(int i=1;i<=n;++i)
        if(match[i]==-1)
        bfs(i);
    for(int i=1;i<=n;++i)
        if(match[i]!=-1)
        pp[vis[i]]++;
    for(int i=1;i<=sz;++i)
    if(pp[i]!=num[i])
        return false;
    return true;
}
void init()
{
    memset(g,0,sizeof(g));
    memset(match,-1,sizeof(match));
}
int abs(int a)
{return a>0?a:-a;}
int main ()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(int i=1;i<=n;++i)
            scanf("%d%d",&x[i],&y[i]);
        scanf("%d",&L);
        for(int i=1;i<=n;++i)
            for(int j=i+1;j<=n;++j)
            if(abs(x[i]-x[j])+abs(y[i]-y[j])<=L)
            {
                g[i][j]=g[j][i]=1;
            }
        memset(num,0,sizeof(num));
        memset(vis,0,sizeof(vis));
        memset(pp,0,sizeof(pp));
        sz=0;
        for(int i=1;i<=n;++i)
            if(!vis[i])
            {
                sz++;
                dfs(i);
            }
        if(solve()) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值