CF1396E Distance Matching【构造】

给定一棵 \(n\) 个点的树,判断是否能将所有点两两匹配使得匹配的点距离和为 \(k\),并构造方案。

\(n \leq 10^5\)\(n\) 为偶数。


先找一下必要条件,考虑计算出答案的上界和下界 \(L\)\(R\),一个显然的结论是 \(L \leq k \leq R\),且 \(L,R,k\) 奇偶性相同。对后者的证明只需要考虑交换两对匹配对答案奇偶性的影响,稍作讨论可知所有匹配的奇偶性都是相同的。

接下来计算 \(L\)\(R\):考虑每条边的贡献,假设这条边将树分为 \(\text{sz}\)\(n - \text{sz}\) 两部分,那么这条边最多被经过 \(\min(\text{sz},n - \text{sz})\) 次,最少被经过 \(\text{sz} \bmod 2\) 次。

\(\min\) 不是很好处理,考虑以重心 \(\text{rt}\) 为根,此时所有子树均有 \(\text{sz} \leq n - \text{sz}\),因此可以得到 \(L = \sum_{i \neq \text{rt}} (\text{sz}_i \bmod 2),R = \sum_{i \neq \text{rt}} \text{sz}_i\)

事实上这两者都是可以取到的:

对于上界,每次取出 \(\text{rt}\) 的儿子中 \(\mathrm{sz}\) 最大的两棵子树中的各 \(1\) 个点进行配对,然后把它们删掉,直到只剩最后 \(1\) 个点和 \(\text{rt}\) 配对即可。这时候每条路径都经过根,并且由于不存在大小超过 \(\frac{n}{2}\) 的子树,因此一定能匹配上。

对于下界,每次取出 \(\text{lca}\) 最深的两个点匹配即可,其正确性容易手玩验证。

然后我们直接猜这个条件就是充要的,考虑用构造的方式证明:

上面所有的讨论只针对答案的奇偶性,为了构造方案现在我们需要对其进行更精确的刻画:考虑一组匹配 \((a_i,b_i)\),设当前根为 \(\text{rt}\),那么答案可以表示为 \(\sum \text{dep}_i - 2 \sum \text{dep}_{\text{lca}(a_i,b_i)}\),由此我们也可以看出上面结论的正确性。

构造过程如下:

\(\mathrm{cur}\) 表示当前答案,初始时 \(\mathrm{cur} = \mathrm{mx}\),当 \(\mathrm{cur} = k\) 时构造结束。

每次在 \(\text{rt}\) 的儿子中 \(\mathrm{siz}\) 最大的子树内,取出 \(\mathrm{lca}\) 深度最大的一个点对进行配对并删除这两个点,然后更新 \(\text{rt}\) 的儿子的 \(\mathrm{siz}\)。根据上面的分析,此时答案减少了 \(2 \mathrm{dep_{lca}}\)

如果某一次操作之后 \(\mathrm{cur} \leq k\) 了,那么我们不进行这次操作,此时我们需要找到一个 \(\mathrm{cur-2dep}=k\)\(\mathrm{lca}\)。只要让原来找到的点往上跳父亲,并把它和它的一个后代匹配即可。

接下来只需要将 \(\text{rt}\) 的所有儿子的子树内的点进行匹配,使得每对点的 \(\mathrm{lca}\) 都是 \(\text{rt}\)。每次找到 \(\mathrm{sz}\) 最大的两棵子树,各取出一个点进行匹配即可。由于之前每次操作都会对最大的子树执行 \(\text{sz} \gets \text{sz} - 2\),因此一定不存在一个子树的 \(\text{sz}\) 大小超过 \(\frac{n}{2}\),所以一定能匹配完。

用 set 维护边集和所有可能的 LCA 即可,时间复杂度 \(O(n \log n)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pb push_back
#define pii pair <int, int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;

const int N = 1e5 + 5;

int n, rt, _x, _y, dep[N], sz[N], mx[N], fa[N]; LL lb, rb, k, cur;
vector <pii> ans; vector <int> vr[N];
set <int> e[N];

struct dat {
    int x;
    bool operator < (const dat &p) const { 
        return sz[x] < sz[p.x]; 
    }
};
priority_queue <dat> q;

struct lca {
    int x;
    bool operator < (const lca &p) const { 
        return dep[x] == dep[p.x] ? x < p.x : dep[x] < dep[p.x]; 
    };
};
set <lca> s[N];

void del(int u, int v) {
    e[u].erase(v), e[v].erase(u);
}
void dfs(int u, int pr) {
    dep[u] = dep[pr] + 1, fa[u] = pr, sz[u] = 1;
    for (int v : e[u]) if (v != pr) {
        dfs(v, u), sz[u] += sz[v]; 
    }
}
void getrt(int u, int pr) {
    sz[u] = 1;
    for (int v : e[u]) if (v != pr) {
        getrt(v, u), sz[u] += sz[v], mx[u] = max(mx[u], sz[v]);
    }
    mx[u] = max(mx[u], n - sz[u]);
    if (mx[u] < mx[rt]) rt = u;
}
void getlca(int u, int p) {
    int c = 0;
    for (int v : e[u]) if (v != fa[u]) getlca(v, p), c++;
    if (c) s[p].insert({u});
}
void get(int u, int p) {
    if (u != _x && u != _y) vr[p].pb(u);
    for (int v : e[u]) if (v != fa[u]) get(v, p); 
}
void link(int i, int j) {
    int x = vr[i].back(), y = vr[j].back();
    ans.pb(mp(x, y));
    vr[i].pop_back(), vr[j].pop_back();
    if (--sz[i]) q.push({i});
    if (--sz[j]) q.push({j});
}
void work() {
    for (int v : e[rt]) get(v, v);
    while (q.size() >= 2) {
        int i = q.top().x; q.pop();
        int j = q.top().x; q.pop();
        link(i, j);
    }
    ans.pb(mp(rt, vr[q.top().x].back()));
    puts("YES");
    for (pii e : ans) printf("%d %d\n", e.fi, e.se);
    exit(0);
}
void solve() {
    if (cur == k) work();
    int t = q.top().x; 
    q.pop();
    if (sz[t] == 1) work();
    sz[t] -= 2;
    int u = (*s[t].rbegin()).x;
    if (cur - 2 * dep[u] <= k) {
        int d = (cur - k) / 2, son;
        for (int v : e[u]) if (v != fa[u]) { son = v; break; }
        while (dep[u] > d) u = fa[u];
        ans.pb(mp(u, son));
        _x = u;
        _y = son;
        if (sz[t]) q.push({t});
        work();
    } else {
        cur -= 2 * dep[u];
        vector <int> son;
        for (int v : e[u]) if (v != fa[u]) {
            son.pb(v);
            if (son.size() == 2) break;
        }
        if (son.size() == 1) {
            ans.pb(mp(u, son[0]));
            del(u, son[0]);
            del(u, fa[u]);
            s[t].erase({u});
            if (e[fa[u]].size() == 1) s[t].erase({fa[u]});
        } else {
            ans.pb(mp(son[0], son[1]));
            del(u, son[0]);
            del(u, son[1]);
            if (e[u].size() == 1) s[t].erase({u});
        }
    }
    if (sz[t]) q.push({t});
}

int main() {
    ios :: sync_with_stdio(0);
    cin >> n >> k;
    for (int i = 1, x, y; i < n; i++) {
        cin >> x >> y;
        e[x].insert(y), e[y].insert(x);
    }
    mx[0] = n;
    getrt(1, 0);
    for (int i = 1; i <= n; i++) sz[i] = 0;
    dep[0] = -1;
    dfs(rt, 0);
    for (int i = 1; i <= n; i++) if (i != rt) lb += sz[i] % 2, rb += sz[i];
    if (k < lb || k > rb || (k + lb) % 2 || (k + rb) % 2) return puts("NO"), 0;
    for (int v : e[rt]) getlca(v, v), q.push({v});
    cur = rb;
    int COP = 1;
    while (COP) solve();
    return 0;   
}
posted @ 2022-10-15 08:13  came11ia  阅读(32)  评论(0编辑  收藏  举报