考虑一个子问题。给定无向图 $G$,如何判断能否将 $G$ 的点集分成两部分 $S$、$T$ 使得 $S$ 和 $T$ 导出的子图都是完全图?
这个问题把我难住了。解法是考虑 $G$ 的补图 $G'$,$G$ 中的完全子图对应于 $G'$ 中的独立集。
$G'$ 的点集能划分为两个独立集等价于 $G'$ 是二分图。
回到原问题。对于补图 $G'$ 的每个连通分量,二分图的两个点集是确定的。于是我们可以通过 DP 算出 $S$ 中可能有几个点。
官方题解:
code
int main() {
int n, m;
scan(n, m);
vv a(n, vi(n));
rep (m) {
int x, y;
scan(x, y);
--x, --y;
a[x][y] = a[y][x] = 1;
}
vv g(n);
rng (i, 0, n) {
rng (j, 0, i) {
if (!a[i][j]) {
g[i].pb(j);
g[j].pb(i);
}
}
}
vi vis(n);
int c1, c2;
vpii num;
function dfs = [&](int u) {
FOR (v, g[u]) {
if (!vis[v]) {
vis[v] = -vis[u];
if (vis[v] == 1) {
++c1;
} else {
++c2;
}
dfs(v);
} else if (vis[v] != -vis[u]) {
println(-1);
exit(0);
}
}
};
rng (i, 0, n) {
if (!vis[i]) {
vis[i] = 1;
c1 = 1, c2 = 0;
dfs(i);
num.eb(c1, c2);
}
}
vi dp(n + 1);
dp[0] = 1;
int limit = 0;
FOR (p, num) {
down (i, limit, 0) {
if (dp[i]) {
dp[i] = 0; // 这里容易错。少了这一句就错了。
dp[i + p.first] = 1;
dp[i + p.second] = 1;
}
}
limit += max(p.first, p.second);
}
int ans = INT_MAX;
rng (i, 0, n + 1) {
if (dp[i]) {
chkmin(ans, i * (i - 1) / 2 + (n - i) * (n - i - 1) / 2);
}
}
println(ans);
return 0;
}
以上实现在 DP 部分采用了滚动数组的技巧,要注意及时清空上一轮的状态。