zoj3316 Game,一般图匹配,带花树

刚学习了一般图匹配的带花树算法。
算法具体过程的讲解请参见fhq大牛博客:
http://fanhq666.blog.163.com/blog/static/8194342620120304463580/

来看这个题zoj3316 Game。
解法是:如果存在完美匹配,则后手赢。否则先手赢。

证明比较简单:
1)不存在完美匹配时,先手胜:
首先把图按照距离小于L的连边,则图可以分为几个连通块。
由于先手可以选择任何位置,如果其中一个连通块先手胜利,则先手胜利。
那么就假设所有点都在一个连通块中。

如果不存在完美匹配,求出的最大匹配是a1&b1,a2&b2...未匹配点是c1,c2..
那么先手取c1,后手只能拆散取a、b中的某个点,这样就拆散了一个匹配。
假设后手取的是a1,那么先手取b1,后手又要拆散某个匹配。
后手不可能再取c集合中的点,否则最大匹配将大于所求匹配数。
这样,后手总有一次取不到点,所以先手胜。

2)如果存在完美匹配,则后手胜:
后手总取先手取的点的匹配点即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define Maxn 370

int set[Maxn];
int vis[Maxn],next[Maxn],mark[Maxn],spouse[Maxn];
int q[Maxn*2],head,tail;
int n;
int tlev=0;

int fi[Maxn],ne[Maxn*Maxn],to[Maxn*Maxn],te;
int g[Maxn][Maxn];

void addedge(int u,int v){te++;ne[te]=fi[u];fi[u]=te;to[te]=v;}

int findset(int x){return x==set[x]?x:set[x]=findset(set[x]);}
void mergeset(int x,int y){x=findset(x);y=findset(y);if (x!=y) set[x]=y;}

int findlca(int u,int v){
    tlev++;
    while(1){
        if (u!=-1){
            u=findset(u);
            if (vis[u]==tlev) return u;
            vis[u]=tlev;
            if (spouse[u]!=-1) u=next[spouse[u]];
            else u=-1;
        }
        swap(u,v);
    }
}

void group(int a,int p){
    int b,c;
    while(a!=p){
        b=spouse[a];c=next[b];
        if (findset(c)!=p) next[c]=b;
        if (mark[b]==2) mark[q[tail++]=b]=1;
        if (mark[c]==2) mark[q[tail++]=c]=1;
        mergeset(a,b);
        mergeset(b,c);
        a=c;
    }
}


void findaugment(int s){
    int i,j,k,l,e,u,v,p;
    for(i=1;i<=n;++i){
        next[i]=-1;set[i]=i;mark[i]=0;vis[i]=-1;
    }
    q[head=0]=s;tail=1;mark[s]=1;
    while(head<tail){
        if (spouse[s]!=-1) break;
        u=q[head++];
        for(e=fi[u];~e;e=ne[e]){
            v=to[e];
            if (spouse[u]!=v&&findset(u)!=findset(v)&&mark[v]!=2){
                if (mark[v]==1){
                    p=findlca(u,v);
                    if (findset(u)!=p) next[u]=v;
                    if (findset(v)!=p) next[v]=u;
                    group(u,p);
                    group(v,p);
                }
                else if (spouse[v]==-1){
                    next[v]=u;
                    for(j=v;j!=-1;){
                        k=next[j];
                        l=spouse[k];
                        spouse[k]=j;spouse[j]=k;
                        j=l;
                    }
                    break;
                }
                else{ //spouse[v]!=-1
                    next[v]=u;
                    mark[q[tail++]=spouse[v]]=1;
                    mark[v]=2;
                }
            }
        }
    }
}

int work(){
    int i,ret;
    for(i=1;i<=n;++i) spouse[i]=-1;
    for(i=1;i<=n;++i)if (spouse[i]==-1) findaugment(i);

    ret=0;
    for(i=1;i<=n;++i)
        if (~spouse[i]) ret++;
    return ret;
}

int x[Maxn],y[Maxn];
int abs(int x){return x<0?-x:x;}

int main(){
    int i,ans,j,l;
    while(scanf("%d",&n)!=EOF){

        for(i=1;i<=n;++i){
            scanf("%d%d",&x[i],&y[i]);
        }
        scanf("%d",&l);
        te=0;
        memset(fi,-1,sizeof(fi));
        for(i=1;i<=n;++i)
            for(j=i+1;j<=n;++j)
                if (abs(x[i]-x[j])+abs(y[i]-y[j])<=l){
                    addedge(i,j);
                    addedge(j,i);
                }

        ans=work();
        if (ans==n) 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、付费专栏及课程。

余额充值