题解:P15653 [省选联考 2026] 星图 / starmap

这也太难了,场上一分没拿到 /ll

谨以这篇题解纪念我死透了的高一赛季。

提供一种异或四元环不需要找欧拉回路的做法。

取原图的补图,转化为最小化边数。

考虑构造一些基础结构。我们发现可以用 \(4\) 次操作异或上一个四元环 \((a,b),(b,c),(c,d),(d,a)\)。具体来说,取 \(k-2\) 个异于 \(a,b,c,d\) 的点 \(x_{1\sim k-2}\),执行下面的操作即可:

\[(x_1,\cdots,x_{k-2},a,b)\\ (x_1,\cdots,x_{k-2},b,c)\\ (x_1,\cdots,x_{k-2},c,d)\\ (x_1,\cdots,x_{k-2},d,a) \]

接下来,考察一个满足如下条件的图:

  • \(2\mid |E|\)
  • \(\forall 1\leq i\leq n,2\mid deg_i\)

我们指出,这样的图必然可以通过若干次异或四元环的操作消成空图。

证明

直接给出构造。

在此之前,我们不妨先考察三元环集合 \(S=\{(1,u),(u,v),(v,1)\mid (u,v)\in E\land u\neq 1\land v\neq 1\}\),该集合内的三元环的异或和恰好就是 \(G\)。这是因为,如果令 \(G'=G\oplus S\),那么 \(G'\) 中点的度数也都是偶数,而显然 \(G'\) 中不含两端都不为 \(1\) 的边,因此 \(G'\) 就是空图。

容易发现 \(S\) 的大小是偶数,考虑给三元环两两配对。对于每对三元环 \((1,a),(a,b),(b,1)\)\((1,c),(c,d),(d,1)\),分类讨论:

  • 若这两个三元环存在恰好一条公共边,那么这两个三元环异或起来恰好就是一个四元环。
  • 若这两个三元环不存在公共边,也就是说 \(a,b,c,d\) 互不相同,那么我们可以将其分解成两个四元环 \((1,a),(a,b),(b,c),(c,1)\)\((1,b),(b,c),(c,d),(d,1)\) 的异或和。

这样我们就得到了构造方案。\(\Box\)

由上述结论,我们发现最小化最终边数,等价于求:最少改变 \(G\) 中多少条边的状态,使得 \(G\) 的边数和每个点的度数都为偶数。

考虑寻找一些不变量:

  • \(2\mid (k-1)\) 时,度数的奇偶性不变。
  • \(2\mid \dbinom k2\) 时,边数的奇偶性不变。

反之:

  • \(2\nmid (k-1)\) 时,可以通过执行 \((x_1,\cdots,x_{k-1},u)\)\((x_1,\cdots,x_{k-1},v)\)\(2\) 次操作来改变 \(deg_u,deg_v\) 的奇偶性。
  • \(2\nmid \dbinom k2\) 时,我们随便执行一次操作即可改变边数的奇偶性。

直接分讨:

  • \(2\nmid (k-1)\land 2\nmid \dbinom k2\Leftrightarrow k\bmod{4}=2\):无需翻转任何边。
  • \(2\nmid (k-1)\land 2\mid \dbinom k2\Leftrightarrow k\bmod{4}=0\):若边数为奇数,则需要任意翻转一条边。
  • \(2\mid (k-1)\land 2\nmid \dbinom k2\Leftrightarrow k\bmod{4}=3\):将奇度点两两配对翻转,也就是说,设有 \(cnt\) 个奇度点,则需要翻转 \(\dfrac {cnt}2\) 条边。
  • \(2\mid (k-1)\land 2\mid \dbinom k2\Leftrightarrow k\bmod{4}=1\):还是先将奇度点两两配对翻转。若操作后图的边数 \(m-\dfrac{cnt} 2\) 为奇数,则需要在不改变度数奇偶性的前提下,改变边数奇偶性。容易想到翻转一个三元环,注意三元环可以和原先的翻转边异或起来,因此这里需要进一步讨论:
    • \(cnt=0\):必须完整地翻转一个三元环,则 \(ans\gets ans-3\)
    • \(cnt>0\):可以让三元环包含一条翻转边,则 \(ans\gets ans-1\)

这样我们就会计算答案了,可以获得 \(25\text{ pts}\)

考虑如何构造方案。注意操作次数上限为 \(\dbinom n2\) 次,这个限制看起来比较烦。

不妨先考虑 \(n=k+2\) 怎么做。此时只有 \(\dbinom nk=\dbinom n2\) 种本质不同的操作,这意味着我们其实可以任意操作。直接按照前文中提到的方法来做:

  • 若边数为奇数,则随便进行一次操作。
  • 将奇度点两两匹配,每对奇度点用两次操作改变奇偶性。
  • 最后将所有不含 \(1\) 的边两两匹配,分解成 \(1\sim 2\) 次异或四元环操作。

过程中记录每种操作被操作次数的奇偶性即可。

接下来做 \(n>k+2\)。考虑直接归纳,把 \(n\) 号点删掉,递归变为子问题。为了保证总操作次数不超过 \(\dbinom n2\) 次,一个规模为 \(n\) 的问题至多只能操作 \(n-1\) 次。不妨考虑 \(n\) 的所有邻点,将邻点两两匹配,对于每一对邻点 \(u,v\),可以通过执行 \((x_1,\cdots,x_{k-2},n,u)\)\((x_1,\cdots,x_{k-2},n,v)\) 这两次操作来断开 \((n,u)\)\((n,v)\) 两条边。那如果 \(deg_n\) 为奇数怎么办呢?考虑在配对之前直接调整,注意到 \(deg_n\) 为奇数时必然有 \(2\mid (k-1)\),那么执行 \((x_1,\cdots,x_{k-1},n)\) 这一操作即可改变 \(deg_n\) 的奇偶性。这里有细节,为了不让这一次额外操作导致操作次数 \(>n-1\),我们需要保证执行额外操作后,\(n\) 的邻点个数 \(<n-1\) 个。这是简单的,执行额外操作时强制选上 \(n\) 的任意一个邻点即可。

这样我们就以至多 \(\dbinom n2\) 次操作解决了本题。

直接模拟上述过程即可,代码其实不算难写。

代码
void starmap(int n, int m, int k, int p, vector<int> u, vector<int> v) {
    for (int i = 1; i <= n; ++i) {
        A[i].reset(), op[i].reset();
        for (int j = 1; j <= n; ++j) A[i][j] = i != j;
    }
    for (int i = 0; i < m; ++i) A[u[i]][v[i]] = A[v[i]][u[i]] = 0;
    int tot = n * (n - 1) >> 1;
    m = tot - m;
    auto flip = [&](int u, int v) { A[u].flip(v), A[v].flip(u); };
    if (k % 4 == 0) {
        int ans = tot;
        if (m & 1) --ans, flip(1, 2);
        report(ans);
    } else if (k % 4 == 2) report(tot);
    else {
        vector<int> O;
        for (int i = 1; i <= n; ++i) if (A[i].count() & 1) O.emplace_back(i);
        int ans = tot;
        for (int i = 0; i < O.size(); i += 2) --ans, flip(O[i], O[i + 1]);
        if (k % 4 == 3) report(ans);
        else {
            int cnt = O.size() >> 1;
            if (m - cnt & 1) {
                int x, y, z;
                if (cnt) {
                    x = O[0], y = O[1], z = 1;
                    while (z == x || z == y) ++z;
                    --ans;
                } else {
                    x = 1, y = 2, z = 3;
                    ans -= 3;
                }
                flip(x, y), flip(y, z), flip(z, x);
            }
            report(ans);
        }
    }
    auto invert_vec = [&](const vector<int> &vec) {
        bitset<MAXN> w;
        for (int i : vec) w[i] = 1;
        for (int i : vec) A[i] ^= w, A[i][i] = 0;
        invert(vec);
    };
    while (n > k + 2) {
        if (A[n].count() & 1) {
            int x = A[n]._Find_first();
            vector<int> vec;
            for (int i = 1; vec.size() < k - 2; ++i)
                if (i != x) vec.emplace_back(i);
            vec.emplace_back(x), vec.emplace_back(n);
            invert_vec(vec);
        }
        vector<int> P;
        for (int i = 1; i <= n; ++i) if (A[n][i]) P.emplace_back(i);
        for (int i = 0; i < P.size(); i += 2) {
            int u = P[i], v = P[i + 1];
            vector<int> vec;
            for (int i = 1; vec.size() < k - 2; ++i)
                if (i != u && i != v) vec.emplace_back(i);
            vec.emplace_back(n);
            vec.emplace_back(u);
            invert_vec(vec);
            vec.pop_back();
            vec.emplace_back(v);
            invert_vec(vec);
        }
        --n;
    }
    m = 0;
    for (int i = 1; i <= n; ++i) m += A[i].count();
    m >>= 1;
    auto ins = [&](const vector<int> &vec) {
        bitset<MAXN> w;
        for (int i : vec) w[i] = 1;
        for (int i : vec) A[i] ^= w, A[i][i] = 0;
        int u = 1;
        while (w[u]) ++u;
        int v = u + 1;
        while (w[v]) ++v;
        op[u].flip(v);
    };
    if (m & 1) {
        vector<int> vec(k);
        iota(vec.begin(), vec.end(), 1);
        ins(vec);
    }
    vector<int> O;
    for (int i = 1; i <= n; ++i) if (A[i].count() & 1) O.emplace_back(i);
    for (int i = 0; i < O.size(); i += 2) {
        int u = O[i], v = O[i + 1];
        vector<int> vec;
        for (int j = 1; vec.size() < k - 1; ++j)
            if (j != u && j != v) vec.emplace_back(j);
        vec.emplace_back(u);
        ins(vec);
        vec.pop_back();
        vec.emplace_back(v);
        ins(vec);
    }
    vector<pair<int, int>> E;
    for (int i = 2; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j)
            if (A[i][j]) E.emplace_back(i, j);
    auto cyc = [&](int a, int b, int c, int d) {
        vector<int> vec;
        for (int i = 1; i <= n; ++i)
            if (i != a && i != b && i != c && i != d) vec.emplace_back(i);
        auto add = [&](int x, int y) {
            vec.emplace_back(x), vec.emplace_back(y);
            ins(vec);
            vec.pop_back(), vec.pop_back();
        };
        add(a, b), add(b, c), add(c, d), add(d, a);
    };
    for (int i = 0; i < E.size(); i += 2) {
        auto [a, b] = E[i];
        auto [c, d] = E[i + 1];
        if (a == c) cyc(1, b, a, d);
        else if (a == d) cyc(1, b, a, c);
        else if (b == c) cyc(1, a, c, d);
        else if (b == d) cyc(1, a, b, c);
        else cyc(1, a, b, c), cyc(1, b, c, d);
    }
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j)
            if (op[i][j]) {
                vector<int> vec;
                for (int x = 1; x <= n; ++x)
                    if (x != i && x != j) vec.emplace_back(x);
                invert(vec);
            }
}
posted @ 2026-03-15 11:27  P2441M  阅读(4)  评论(0)    收藏  举报