Loading

5.15 CW 模拟赛 T3. 匹配

思路

首先考虑约束条件

注意到其要求任意选择都要能做成匹配
也就是只要我选择了一些端点不相交的边, 那么剩下的一定要有解

这个约束太麻烦, 必须找到性质
pEjTu6J.png

补充一句, 应该是任意合法连通块, 不连通显然无所谓

那么问题变得拟人了
只需要把联通块拼成一些集合使得每个集合两边点数相等, 然后最小化每个集合一边点数平方和, 最后减去原来的边数就可以了

\(\rm{dp}\) 发现可能的连通块情况只有 \(40000\) 多种, 是很好的
剩下是乱搞, 这里采用暴力合并连通块加上一些剪枝

代码
#include <bits/stdc++.h>

const int MAXN = 1e5 + 20;

char s[MAXN];
int t, n, m, fa[MAXN], lsiz[MAXN], rsiz[MAXN], ans, sum, vis[MAXN];

std::vector <std::pair<int, int> > v;

inline int calc(int x) { return x * x; }

inline void init() {
    for (int i = 1; i <= (n << 1); i++)
        fa[i] = i, lsiz[i] = (i <= n), rsiz[i] = (i > n);
}

inline int find(int x) {
    while (x != fa[x])
        x = fa[x] = fa[fa[x]];
    return x;
}

inline void merge(int x, int y) {
    x = find(x); y = find(y);
    if (x != y) {
        fa[x] = y;
        lsiz[y] += lsiz[x];
        rsiz[y] += rsiz[x];
    }
}

void dfs(int k, int lst, int tot, int ltot, int rtot, int delta) {
    if ((sum + std::max(ltot, rtot)) >= ans) return;
    if (!(~lst)) {
        if (k == v.size()) { ans = std::min(ans, sum + ltot); return; }
    } else {
        sum += calc(tot + std::max(0, delta));
        if (delta > 0) {
            if (ltot >= delta) dfs(k, -1, 0, ltot - delta, rtot, 0);
        } else {
            if ((rtot + delta) >= 0) dfs(k, -1, 0, ltot, rtot + delta, 0);
        } // 回溯
        sum -= calc(tot + std::max(0, delta));
    }
    std::pair<int, int> now = {0, 0};
    for (int i = lst + 1; i < v.size(); i++)
        if (!vis[i] && v[i] != now) {
            now = v[i]; vis[i] = 1;
            dfs(k + 1, i, tot + v[i].first, ltot, rtot, delta - v[i].first + v[i].second);
            vis[i] = 0;
        }
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n); init(); v.clear();
        ans = n * n; sum = m = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%s", s + 1);
            for (int j = 1; j <= n; j++)
                if (s[j] == '1') { m++; merge(i, j + n); }
        }
        int ltot = 0, rtot = 0;
        for (int i = 1; i <= (n << 1); i++)
            if (fa[i] == i) {
                if (lsiz[i] == rsiz[i])
                    sum += calc(lsiz[i]);
                else if (!lsiz[i]) rtot++;
                else if (!rsiz[i]) ltot++;
                else v.push_back({lsiz[i], rsiz[i]});
            }
        sort(v.begin(), v.end());
        dfs(0, -1, 0, ltot, rtot, 0);
        printf("%d\n", ans - m);
    }
    return 0;
}

总结

找性质的时候应该要不断把性质加强

posted @ 2025-05-15 21:09  Yorg  阅读(19)  评论(0)    收藏  举报