BZOJ 1854: [Scoi2010]游戏 二分图匹配or并查集

时空隧道


分析:
Method1:二分图匹配
我们建图满足的要求是可以保证每个武器只能使用一次,第一想法是把一个武器拆成两个点,但是这样怎么保证每个点只选一次呢?首先就要在两个点之间建立联系…连边?然后呢?只能选其中一个点?感觉好像不好搞的样子….
或许我们可以借助一个点把这两个点建立联系,就是从一个点向这两个点连边,那么只能选一条边?感觉像是二分图匹配?
bingo!
我们把武器编号作为左部节点,武器属性值作为右部节点,对于每个武器,向它的两个属性值连边,然后从1到10000去跑二分图匹配,如果匹配不了就输出ans


代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;
const int maxn=1000010+5;
int n,hd[maxn],to[maxn*2],nxt[maxn*2],cnt,vis[maxn],pre[maxn],ans,sta;
inline void add(int x,int y){
    to[cnt]=y;
    nxt[cnt]=hd[x];
    hd[x]=cnt++;
}
inline bool dfs(int root){
    for(int i=hd[root];i!=-1;i=nxt[i])
        if(vis[to[i]]!=sta){
            vis[to[i]]=sta;
            if(pre[to[i]]==-1||dfs(pre[to[i]])){
                pre[to[i]]=root;
                return true;
            }
        }
    return false;
}
signed main(void){
    cnt=sta=0;
    scanf("%d",&n);
    memset(hd,-1,sizeof(hd));
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    for(int i=1,x,y;i<=n;i++)
        scanf("%d%d",&x,&y),add(x,i),add(y,i);
    ans=10000;
    for(int i=1;i<=10000;i++){
        sta++;
        if(!dfs(i)){
            ans=i-1;
            break;
        }
    }
    cout<<ans<<endl;
    return 0;
}


Method2:并查集
在上一个方法中我们是用武器编号这个节点在两个属性值之间建立联系,我们也可以借助一条边把这两个属性值建立联系,可以理解为一条边代表一个武器,然后如果一个连通块中有一个n个节点的树,那么代表有n-1个武器可以使用,如果是一个环呢?代表n个武器可以使用…
如果每次加入的边是合并两个联通块
就把权值小的联通块并到权值大的联通块,然后给权值小的vis=true
如果不是就把该联通块的顶点的vis=true
这样就可以保证,如果一个大小为N联通块
=N-1条边构成,最大点的vis=false,其他为true
≥N条边构成,所有点的vis=true
然后最后只要一次扫描vis就可以得出答案了


代码如下:
(借用YOUSIKI童鞋的代码)

/*
 * You Siki
 * Born to be King!
 */

#include<cstdio>
#include<cstdlib>
#include<cstring>

//using namespace std;

const int LIMIT = 10000000;

char in[LIMIT], *p = in;

signed main(void) {

    fread(p, 1, LIMIT, stdin);

    register int n = 0;

    while (*p < '0')++p;

    while (*p >= '0')
        n = n * 10 + *p++ - '0';

    register bool visit[10005];
    register short father[10005];
    register short stack[10005], tot = 0;   

    memset(visit, 0, sizeof(visit));

    for (register int i = 1; i <= 10000; ++i)father[i] = i;

    for (register int i = 1, x, y; i <= n; ++i) {

        x = y = 0;

        while (*p < '0')++p;

        while (*p >= '0')
            x = x * 10 + *p++ - '0';

        while (x ^ father[x])
            stack[++tot] = x, x = father[x];

        while (tot)father[stack[tot--]] = x;

        while (*p < '0')++p;

        while (*p >= '0')
            y = y * 10 + *p++ - '0';

        while (y ^ father[y])
            stack[++tot] = y, y = father[y];

        while (tot)father[stack[tot--]] = y;

        if (x ^ y) {
            if (x > y)
                visit[visit[y] ? x : y] = true, father[y] = x;
            else
                visit[visit[x] ? y : x] = true, father[x] = y;
        }
        else visit[x] = true;

    }

    register int ans = 0;

    while (visit[++ans]);

    printf("%d\n", --ans);

}

by >_< NeighThorn

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值