POJ 3041 Asteroids

次元传送门


题目大意:
有一个n*n的棋盘,给出一些点的位置,询问最少去掉多少行和列,可以把所有点去掉,输出行列总数


分析:
这道题,棋盘,点,行,列,有没有想到什么??
没错
二分图………….
最大匹配………..
很惭愧(⊙﹏⊙)b,自己太弱了,对二分图理解不够深刻,没看出来这是个二分图>_<,在YOUSIKI童鞋的友情提示下才知道此题要用二分图解(ˇˍˇ) …………….
所以接下来就要说一说怎么求二分图最大匹配
锵锵锵锵!!!(^o^)/~匈牙利算法闪亮登场
先来说一说匈牙利算法的最核心思想吧—–找增广路
(⊙o⊙)…额,什么是增广路?_?
先要说一下什么叫做交替路:
在已有匹配P的二分图中,存在路径S,v1、v2、v3、v4……∈S,v1∈P,v2∉P,v3∈P,v4∉P……,那么S就是一条交替路
所谓增广路,就是起点和终点都∉P的一条交替路
下图中,9->4->8->1->6->2就是一条增广路
这里写图片描述
增广路一定是奇数条边,因为除了起点和终点其他点都是匹配点,一定存在奇数条边(偶数条匹配边,这是显然的,奇数条未匹配边),再加上由起点和终点衍生出来的两条未匹配边,一定是奇数条边……..
仔细观察,我们会发现,增广路中未匹配路径比匹配路径要多一条,这是很显然的……………因为刚刚我们说明了,除了起点和终点其他点都是匹配点,一定存在奇数条边(偶数条匹配边,这是显然的,奇数条未匹配边),并且未匹配边的个数比匹配边的个数少一,再加上由起点和终点衍生出来的两条未匹配边,所以未匹配边的个数比匹配边的个数多一…………..
有了以上性质,我们就可以计算最大匹配了
首先说有向边:
每一次从一个点出发,寻找增广路,如果找到了增广路,证明匹配数需要+1………………然后就没有然后了
等等!!有个重要问题!!怎么找增广路呢??
依次考虑每个左部节点,为其找到一个右部节点与之匹配。
一个右部节点能与之匹配,必满足一下两个条件之一:
1、这个节点尚未与任何左部节点匹配,此时直接把两个节点进行匹配
2、从该节点匹配的左部节点出发,可以找到一个未标记的右部节点与之匹配,此时给该节点打上标记,递归进入那个左部节点,为它寻找匹配节点

代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=500+5,maxm=10000+5;
int n,k,hd[maxn*2],to[maxm],nxt[maxm],ans,vis[maxn*2],cnt,pre[maxn*2];
inline int read(void){
    char ch=getchar();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return f*x;
}
inline void add(int x,int y){
    to[cnt]=y;
    nxt[cnt]=hd[x];
    hd[x]=cnt++;
}
bool dfs(int u){
    for(int i=hd[u];i!=-1;i=nxt[i]){
        if(!vis[to[i]]){
            vis[to[i]]=true;
            if(pre[to[i]]==-1||dfs(pre[to[i]])){
                pre[to[i]]=u;
                return true;
            }
        }
    }
    return false;
}
signed main(void){
    memset(hd,-1,sizeof(hd)),cnt=0;
    memset(pre,-1,sizeof(pre));
    n=read(),k=read();
    while(k--)
        add(read(),read()+n);
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if(dfs(i))
            ans++;
    }
    cout<<ans<<endl;
    return 0;
}

接下来说无向边:
考虑每个未匹配的左部节点,为其寻找一个可以匹配的右部节点,右部节点的匹配条件相同
这里借用一下YOUSIKI童鞋的代码(自己懒得再写一遍了(⊙o⊙)…懒癌晚期……….),希望YOUSIKI童鞋不要收版权费>_<

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1005,M=20005;
int n,m,hd[N],to[M],nt[M],tot,mch[N],chk[N];
inline void addEdge(int x,int y){nt[tot]=hd[x],to[tot]=y,hd[x]=tot++;}
inline bool dfs(int u){
    for(int i=hd[u];~i;i=nt[i])if(!chk[to[i]]){
        chk[to[i]]=1;if(mch[to[i]]==-1||dfs(mch[to[i]])){
            mch[u]=to[i],mch[to[i]]=u;return true;}
    }return false;
}
signed main(void){
    scanf("%d%d",&n,&m);
    memset(hd,-1,sizeof(hd)),tot=0;
    for(int i=1,x,y;i<=m;i++)
        scanf("%d%d",&x,&y),y+=n,
        addEdge(x,y),addEdge(y,x);
    int ans=0;memset(mch,-1,sizeof(mch));
    for(int i=1;i<=n;i++)if(mch[i]==-1){
        memset(chk,0,sizeof(chk));
        if(dfs(i))ans++;
    }printf("%d\n",ans);
}//代码好短>_<.............

by >o< neighthorn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值