11.5 AM 请求
题意
给定一个图,请把它分为两个子图,使得每一个子图都是完全图;并且要求这两个图的边数之和最少
解法
先建出原图的反图,即有边连接的两个点之间不连边,没有边连接的两个点之间连一条边
可以发现这个图可以被拆成两个完全图当且仅当每一个联通块都是一个二分图
因为如果不是二分图(存在奇环)的话,一定有奇数个点之间互不连边,那么无论如何也无法把它们分入两个集合使得这两个集合均为完全图
二分图染色后的新图中,同一个联通块且是相同颜色的点一定在同一个子图中,而不同联通块中即使颜色不同也可以放在一起,因为两个联通块中的点一定是互有连边的
设 \(f_i\) 表示是否存在一个分组方式使得其中有一组的大小为 \(i\),枚举联通块背包转移即可
代码
#include <cstdio>
#include <cctype>
#include <bitset>
#include <cstring>
#include <iostream>
using namespace std;
const int MAX_N = 800;
const int MAX_M = 700000;
int read();
typedef bitset<MAX_N> Bit;
int N, M, fl, cnt;
int col[MAX_N], sz[2];
int head[MAX_N], to[MAX_M], nxt[MAX_M], cap;
Bit e[MAX_N], f[MAX_N];
inline void link(int u, int v) { to[++cap] = v, nxt[cap] = head[u], head[u] = cap; }
void DFS(int x, int cr, int* sz) {
col[x] = cr, sz[cr]++;
for (int i = head[x]; i; i = nxt[i]) {
if (col[to[i]] == -1) DFS(to[i], cr ^ 1, sz);
else if (col[to[i]] == cr) fl = 1;
}
}
int main() {
N = read(), M = read();
for (int i = 1; i <= M; ++i) {
int u = read(), v = read();
e[u][v] = e[v][u] = 1;
}
memset(col, -1, sizeof col);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= N; ++j) if ((i ^ j) && !e[i][j]) link(i, j);
f[0][0] = 1;
for (int i = 1; i <= N; ++i) {
if (col[i] != -1) continue;
sz[0] = sz[1] = 0;
DFS(i, 0, sz);
if (fl) return puts("-1"), 0;
++cnt;
for (int j = 0; j <= N; ++j) {
if (j + sz[0] <= N && f[cnt - 1][j]) f[cnt][j + sz[0]] = 1;
if (j + sz[1] <= N && f[cnt - 1][j]) f[cnt][j + sz[1]] = 1;
}
}
int ans = 0x3f3f3f3f;
for (int i = 1; i < N; ++i)
if (f[cnt][i]) ans = min(ans, i * (i - 1) / 2 + (N - i) * (N - i - 1) / 2);
printf("%d\n", ans);
return 0;
}
int read() {
int x = 0, c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
return x;
}