zoj3316【一般图最大匹配 带花树开花】

链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3726

算法见:http://fanhq666.blog.163.com/blog/static/8194342620120304463580/

题意:棋盘上有n个点,现在两个人轮流在移除一个子,每次移除的子距离上次移除的子曼哈顿距离小于L。最后不能移除的人输。

分析:将距离小于L的点连边。如果一个连通块不是是完备匹配,先手一定可以让自己走到一个没有匹配的点上,然后后手就不能移除子了,如果后手可以移除子,那么就有了新的匹配,现在就是一个完备匹配了。

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<vector>
#include<queue>
#include<cmath>
#include<stack>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define Mn 400
#define Mm 200005
#define mod 1000000007
#define CLR(a,b) memset((a),(b),sizeof((a)))
#define CPY(a,b) memcpy ((a), (b), sizeof((a)))
#pragma comment(linker, "/STACK:102400000,102400000")
#define ul u<<1
#define ur (u<<1)|1
using namespace std;
typedef long long ll;
struct edge {
    int v,next;
}e[Mm];
struct point {
    int x,y;
}p[Mn];
int dis(point a,point b) {
   return abs(a.x-b.x)+abs(a.y-b.y);
}
int tot,head[Mn];
void addedge(int u,int v) {
    e[tot].v=v;
    e[tot].next=head[u];
    head[u]=tot++;
}
int pre[Mn];
int findpre(int x) {
    return x==pre[x]?pre[x]:pre[x]=findpre(pre[pre[x]]);
}
void ol(int a,int b) {
    a=findpre(a);
    b=findpre(b);
    if(a!=b) pre[a]=b;
}
int n,lk[Mn],vis[Mn],mark[Mn],match[Mn],ne[Mn];
int lca(int x,int y) {
    static int t=0;t++;
    while(1) {
        if(x!=-1) {
            x=findpre(x);
            if(vis[x]==t) return x;
            vis[x]=t;
            if(match[x]!=-1) x=ne[match[x]];
            else x=-1;
        }
        swap(x,y);
    }

}
queue<int> q;
void group(int a,int p) {
    while(a!=p) {
        int b=match[a],c=ne[b];
        if(findpre(c)!=p) ne[c]=b;
        if(mark[b]==2) mark[b]=1,q.push(b);
        if(mark[c]==2) mark[c]=1,q.push(c);
        ol(a,b),ol(b,c);
        a=c;
    }
}
void aug(int s) {
    for(int i=0;i<=n;i++) {
        ne[i]=-1;
        pre[i]=i;
        mark[i]=0;
        vis[i]=-1;
    }
    mark[s]=1;
    while(!q.empty()) q.pop();
    q.push(s);
    while((!q.empty())&&match[s]==-1) {
        int u=q.front();
        q.pop();
        for(int i=head[u];~i;i=e[i].next) {
            int v=e[i].v;
            if(match[u]==v||findpre(u)==findpre(v)||mark[v]==2) continue;
            if(mark[v]==1) {
                int r=lca(u,v);
                if(findpre(u)!=r) ne[u]=v;
                if(findpre(v)!=r) ne[v]=u;
                group(u,r);
                group(v,r);
            } else if(match[v]==-1) {
                ne[v]=u;
                for(int x=v;~x;) {
                    int y=ne[x];
                    int mv=match[y];
                    match[x]=y,match[y]=x;
                    x=mv;
                }
                break;
            } else {
                ne[v]=u;
                q.push(match[v]);
                mark[match[v]]=1;
                mark[v]=2;
            }
        }
    }
}
void init() {
    tot=0;
    CLR(head,-1);
}
int main() {
    while(~scanf("%d",&n)) {
        int l;init();
        for(int i=1;i<=n;i++)
            scanf("%d%d",&p[i].x,&p[i].y);
        scanf("%d",&l);
        for(int i=1;i<n;i++) {
            for(int j=i+1;j<=n;j++) {
                if(dis(p[i],p[j])<=l) {
                    addedge(i,j);
                    addedge(j,i);
                }
            }
        }
        for(int i=1;i<=n;i++) match[i]=-1;
        for(int i=1;i<=n;i++) {
            if(match[i]==-1) aug(i);
        }
        int ans=0;
        for(int i=1;i<=n;i++) {
            if(match[i]!=-1) ans++;
        }
        if(ans==n) {
            printf("YES\n");
        } else printf("NO\n");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值