https://www.luogu.org/problemnew/show/U21403
有标号为 $1, 2, ..., n$ 的小球,每个小球的标号 $x$ 可以替换为任意一个 $x$ 的倍数,请你找到一种变换,使得【标号 $x$ 的出现次数为奇数】的情况尽可能少。
$n \le 10 ^ 3$
相当于可以将小球划分到任意一个其倍数的集合内,使得大小为奇数的集合尽可能少,即
$$\sum_x (x \mod 2)$$
变形为
$$\sum_x (x - 2 \lfloor \frac{x}{2} \rfloor) = n - 2 \sum_x \lfloor \frac{x}{2} \rfloor$$
暴力的想法是对同一个集合内的点两两连边,成为一个团,跑一般图最大匹配,但是会 TLE 。
考虑优化建图,对每个集合,把集合中的点排成一个环,在两个相邻的点之间建立一个辅助点,辅助点之间连边成环,一个辅助点与相邻两个点连边。若这个块剩下 $x$ 个点,那么这个块的最大匹配为 $\lfloor \frac{x + k}{2} \rfloor = \lfloor \frac{x}{2} \rfloor - \frac{k}{2}$ 。
#include <bits/stdc++.h> using namespace std; #define F(i, s, t) for (int i = (s), _ = (t); i <= _; i ++) #define Fo(i, s, t) for (int i = (s), _ = (t); i < _; i ++) #define pb push_back const int N = 20000; int n, tot; vector<int> li[N], gr[N]; int ans, mat[N], u[N]; void Init(int x, int y) { gr[x].pb(y), gr[y].pb(x); } int DFS(int x) { u[x] = 1; for (int v : gr[x]) { int w = mat[v]; if (! w) return mat[x] = v, mat[v] = x, 1; if (! u[w]) { mat[x] = v, mat[v] = x, mat[w] = 0; if (DFS(w)) return 1; mat[x] = 0, mat[v] = w, mat[w] = v; } } return 0; } int main() { #ifndef ONLINE_JUDGE //freopen("403.in", "r", stdin); #endif cin >> n, tot = n; F(i, 1, n) for (int j = i; j <= n; j += i) li[j].pb(i); F(cen, 1, n) { int s = li[cen].size(), c = s + (s & 1); Fo(i, 0, c) Init(tot + 1 + i, tot + 1 + (i + 1) % c); Fo(i, 0, s) { Init(li[cen][i], tot + 1 + i); Init(li[cen][i], tot + 1 + (i + 1) % c); } tot += c; } ans = - (tot - n) >> 1; F(i, 1, tot) if (! mat[i]) { memset(u, 0, sizeof u); ans += DFS(i); } cout << n - 2 * ans << endl; return 0; }