5.15 CW 模拟赛 T3. 匹配
思路
首先考虑约束条件
注意到其要求任意选择都要能做成匹配
也就是只要我选择了一些端点不相交的边, 那么剩下的一定要有解
补充一句, 应该是任意合法连通块, 不连通显然无所谓
那么问题变得拟人了
只需要把联通块拼成一些集合使得每个集合两边点数相等, 然后最小化每个集合一边点数平方和, 最后减去原来的边数就可以了
\(\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;
}
总结
找性质的时候应该要不断把性质加强


浙公网安备 33010602011771号