P2423 [HEOI2012] 枚举 + 二分图

这篇博客介绍了如何利用二分图的最大匹配来解决最大团问题。通过将原图转换为其补图,然后利用二分图的性质,将问题转化为求解最大独立集。博主详细阐述了算法流程,包括如何建立二分图、计算最大匹配,并通过实例展示了如何进行点覆盖优化,以达到较高的时间效率。最后,给出了完整的C++代码实现。
摘要由CSDN通过智能技术生成
题意

传送门 P2423 [HEOI2012]朋友圈

题解

A 国是一个二分图,左右部节点奇偶性不同,则在图的最大团中,左部、右部各至多包含一个节点。

B 国的补图是一个二分图,左右部节点奇偶性不同。原图的最大团等于补图的最大独立集,最大独立集等于图中节点数减去最小点覆盖。二分图中最小点覆盖等于最大二分匹配,那么求最大团的问题最终转化为求二分图的最大匹配。

枚举 A 国在最大团中的点集 S S S,其规模最多为 2 2 2,仅考虑与 S S S 所有点都连边的 B B B 国中的节点,求最大团即可。

总时间复杂度 O ( A 2 B 3 ) O(A^2B^3) O(A2B3)。实际上是一个较宽松的上界,求增广路的访问标记可以通过时间戳进行优化。

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const int MAXN = 3005;
int T, A, B, M, clk;
int ax[MAXN], bx[MAXN];
bool fr[MAXN][MAXN], can[MAXN];
vector<int> G[MAXN];
int match[MAXN], used[MAXN];

bool dfs(int v)
{
    used[v] = clk;
    for (int u : G[v])
    {
        if (!can[u])
            continue;
        int w = match[u];
        if (w == -1 || (used[w] != clk && dfs(w)))
        {
            match[v] = u, match[u] = v;
            return 1;
        }
    }
    return 0;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        cin >> A >> B >> M;
        for (int i = 0; i < A; ++i)
            cin >> ax[i];
        for (int i = 0; i < B; ++i)
            cin >> bx[i];
        for (int i = 0; i < A; ++i)
            for (int j = 0; j < B; ++j)
                fr[i][j] = 0;
        for (int i = 0; i < M; ++i)
        {
            int u, v;
            cin >> u >> v;
            --u, --v;
            fr[u][v] = 1;
        }
        for (int i = 0; i < B; ++i)
            G[i].clear();
        for (int i = 0; i < B; ++i)
            for (int j = i + 1; j < B; ++j)
                if ((bx[i] & 1) != (bx[j] & 1) && !(__builtin_popcount(bx[i] | bx[j]) & 1))
                    G[i].pb(j), G[j].pb(i);

        clk = 0;
        for (int i = 0; i < B; ++i)
            used[i] = -1, can[i] = 1, match[i] = -1;
        int m = 0;
        for (int i = 0; i < B; ++i)
            if (bx[i] & 1)
                m += dfs(i);
        int res = B - m;
        for (int i = 0; i < B; ++i)
            can[i] = 0;
        ++clk;

        for (int i = 0; i < A; ++i)
        {
            vector<int> vs;
            for (int j = 0; j < B; ++j)
                if (fr[i][j])
                    vs.pb(j);
            for (int v : vs)
                can[v] = 1, match[v] = -1;
            int m = 0;
            for (int v : vs)
                if ((bx[v] & 1) && can[v])
                    m += dfs(v);
            res = max(res, (int)vs.size() - m + 1);
            for (int v : vs)
                can[v] = 0;
            ++clk;
        }

        for (int i = 0; i < A; ++i)
            for (int j = i + 1; j < A; ++j)
            {
                if ((ax[i] & 1) == (ax[j] & 1))
                    continue;
                vector<int> vs;
                for (int v = 0; v < B; ++v)
                    if (fr[i][v] && fr[j][v])
                        vs.pb(v);
                for (int v : vs)
                    can[v] = 1, match[v] = -1;
                int m = 0;
                for (int v : vs)
                    if ((bx[v] & 1) && can[v])
                        m += dfs(v);
                res = max(res, (int)vs.size() - m + 2);
                for (int v : vs)
                    can[v] = 0;
                ++clk;
            }

        cout << res << '\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值