Topcoder 2017 TCO Algorithm Round 3A Hard

链接:

link

题意:

给一个 n×m 01 矩阵,两个 1 能匹配当且仅当它们在同一行或者同一列,一个矩阵是好的当且仅当它有唯一的完美匹配,求最少把多少个0改成 1 让这个矩阵是好的。

题解:

考虑构建一个行列的二分图,如果ai,j=1就从左边的 i 点到右边的j点连一条边,显然这个图不能有环。
最后我们就是要把边分配到点上,使得点的度数为 0 或者2,那么我们考虑枚举最后新加的边分配到那些点,共 2n+m 种方案。
这样我们就可以快速check是否合法了,然后我们再考虑还原边。
假设A部有 lef 个点度数为 0 ,B部有rig个点度数为 0 ,剩下的连通块数为cnt,那么我们如果增加了左边的点的度数,一定会尽量去连接右边的 rig 个孤立点,再每次连接两个连通块,不难得出式子:
max(lrig,0)+max(rlef,0)<cnt ,其中 l r是给两边分配的度数。

代码:

#include <bits/stdc++.h>
#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define mset(x, y) memset(x, y, sizeof x)
#define mcpy(x, y) memcpy(x, y, sizeof x)
using namespace std;

typedef long long LL;
typedef pair <int, int> pii;

const int MAXN = 30;

int n, m, tot, cnt, f[MAXN];
vector <int> adj[MAXN];
bool vis[MAXN], flg;

inline int Find(int x)
{
    while (x ^ f[x])
        x = f[x] = f[f[x]];
    return x;
}

inline int Dfs(int x, int p, int S)
{
    vis[x] = 1;
    int cur = S >> x & 1;
    for (auto y : adj[x])
        if (y ^ p)
            cur += Dfs(y, x, S);
    if (cur > 2)
        flg = false;
    return (cur & 1) ^ 1;
}

inline bool Chk(int S)
{
    flg = true;
    for (int i = 0; i < tot; i ++)
        vis[i] = false;
    for (int i = 0; i < tot; i ++)
        if (!vis[i])
            if (!Dfs(i, -1, S) || !flg)
                return false;
    return true;
}

class Permatch
{
    public:
        int complete(vector <string> board) 
        {
            n = board.size(), m = board[0].length(), cnt = tot = n + m;
            for (int i = 0; i < tot; i ++)
                f[i] = i, adj[i].clear();
            for (int i = 0; i < n; i ++)
                for (int j = 0; j < m; j ++)
                    if (board[i][j] == '#')
                    {
                        if (Find(i) == Find(j + n))
                            return -1;
                        f[Find(i)] = Find(j + n);
                        adj[i].pb(j + n); adj[j + n].pb(i);
                        cnt --;
                    }
            int sin = 0, lef = 0, rig = 0, ans = tot + 1;
            for (int i = 0; i < tot; i ++)
                if (adj[i].empty())
                    sin |= 1 << i, cnt --;
            if (!cnt)
                return 0;
            lef = __builtin_popcount(sin & ((1 << n) - 1)), rig = __builtin_popcount(sin >> n);
            for (int i = 0; i < 1 << tot; i ++)
                if (!(i & sin) && Chk(i))
                {
                    int l = __builtin_popcount(i & ((1 << n) - 1)), r = __builtin_popcount(i >> n);
                    if (max(l - rig, 0) + max(r - lef, 0) < cnt)
                        ans = min(ans, __builtin_popcount(i));
                }
            if (ans == tot + 1)
                return -1;
            return ans;
        }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值