洛谷 P1640 [SCOI2010]连续攻击游戏 二分图最大匹配 || 并查集

题目链接

题意:有 n n n 个武器,每个武器有两种属性值,每种武器只能使用一次,且只能使用一种属性值,现在用这 n n n 种武器攻击,要求只有使用过 i i i 属性值攻击才能使用 i + 1 i+1 i+1 属性值攻击( 1 ≤ i 1 \leq i 1i ),现在求最大能使用到多少属性值进行攻击。

思路:

第一种:

我们可以把一种属性值当作一个点,同时一个武器当作一个点,把武器和他具有的两个属性值相连,问题就变成求这个二分图最大匹配了。

但是题目有条件约束必须选了 i i i 属性值才能选 i + 1 i+1 i+1 。这里我们就可以用匈牙利算法求最大匹配,因为匈牙利每次算当前点的增广路时可以保证之前的所有点已经匹配完,这样就可以满足题目所给约束条件。

#include<cstdio>
#include<vector>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define NUM 10005
#define MAXN 1000005
int se1[NUM], se2[MAXN];
vector<int> e[NUM];
int n, ans = 0;
bool vis2[MAXN] = {};
bool dfs(const int v)
{
    int u, sz = e[v].size();
    ffor(i, 0, sz - 1)
    {
        u = e[v][i];
        if(vis2[u])
            continue;
        vis2[u] = true;
        if (se2[u] == -1 || dfs(se2[u]))
        {
            se2[u] = v, se1[v] = u;
            vis2[u] = false;
            return true;
        }
        vis2[u] = false;
    }
    return false;
}
inline void AC()
{
    int x, y;
    scanf("%d", &n);
    ffor(i, 1, n)
    {
        scanf("%d%d", &x, &y);
        e[x].push_back(i), e[y].push_back(i);
    }
    memset(se1, -1, sizeof(se1)), memset(se2, -1, sizeof(int) * (n + 1));
    ffor(i, 1, 10000)
        if (se1[i] != -1 || dfs(i))
            ++ans;
        else
            break;
    printf("%d\n", ans);
}
int main()
{
    AC();
    return 0;
}
第二种:

把一种属性值当作一个点,把一个武器当作一条边,连接武器具有的两个属性值。

一个武器选择一种属性值使用,就相当于一条边匹配它相连的其中一个点。

显然,在一个连通块里,当边的数目大于点的数目时,即存在环,则这连通块里的所有点都能被匹配,若等于点的数目,则有一个点不能被匹配,根据题目约束条件,我们应该选择最大的点不能被匹配。

判断连通块是否有环,以及连通块里的最大的点就用并查集维护。

#include<cstdio>
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define NUM 10005
int n, f[NUM], num[NUM];
bool flag[NUM] = {};
int sta[NUM];
template <typename T>
void read(T &x){
    char ch = getchar();x = 0;
    for (; ch < '0' || ch > '9'; ch = getchar());
    for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}
namespace HH
{
int find(int x)
{
    while (f[x] != x)
        sta[++sta[0]] = x, x = f[x];
    while (sta[0])
        f[sta[sta[0]--]] = x;
    return x;
}
inline void Union(const int &x, const int &y)
{
    int fx = find(x);
    int fy = find(y);
    if (fx == fy)
    {
        flag[fx] = true;
        return;
    }
    if (num[fx] > num[fy])
    {
        flag[fx] |= flag[fy];
        f[fy] = fx;
        num[fx] += num[fy];
    }
    else
    {
        flag[fy] |= flag[fy];
        f[fx] = fy;
        num[fy] += num[fx];
    }
}
inline void AC()
{
    int x, y;
    read(n);
    sta[0] = 0;
    ffor(i, 1, 10000) f[i] = i, num[i] = 1;
    ffor(i, 1, n)
    {
        read(x), read(y);
        Union(x, y);
    }
    int ans = 10000;
    ffor(i, 1, 10000)
    {
        int fi = find(i);
        if(!flag[fi]) 
        {
            if (num[fi] == 1)
            {
                ans = i - 1;
                break;
            }           
            --num[fi];
        }
    }
    printf("%d\n", ans);
}
}
int main()
{
    HH::AC();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值