1 条题解
-
0
题意简述:
给定一棵 个点的树,初始每个点上有一只青蛙,每只青蛙一开始都是绿色的。
一只青蛙可以跳跃一次到与其所在点相邻的点并改变颜色,若是绿色青蛙则变成棕色,反之变成绿色。一只青蛙最多跳跃 次。
如果两只在同一个点的青蛙颜色不同则可以组成一对合唱队,每只青蛙最多加入一对合唱队。
现在请求出能组成的最大合唱队数以及每一对是哪两只青蛙组成的。
定义 为 和 两点间的距离。显然只有两点深度差为奇数时两点上的青蛙跳到一起才能满足限制。
所有点上初始都有一只青蛙,我们有贪心:若某两点 上的青蛙可以以 的跳跃次数跳到一起,则将两点的 LCA 向上提一定不劣。
证明形如,LCA 向上提后 增加 奇偶性不变,同时原来保证 为奇数且 则有 。
将点按深度奇偶分组,两组之间的深度差显然为奇数,两集合做启发式合并得到一个 的做法。
用将子节点集合并入当前点集合的实现太不优美了!考虑更换实现,把 DSU 替换成轻重儿子的处理形式。去掉维护奇偶的集合,用 个全局集合维护每个深度的节点信息。
建立一个从深度和奇偶性到节点的映射,当一个深度为 的子节点合并至当前深度 的节点 时,我们精细查找里面满足 且最大的 ,需要保证奇偶性所以映射分了奇偶。当这两个点可以达到可合并的最大距离 时应在深度为 时相遇。把这个深度挂在一个东西上等算到这个深度的时候更新。
但是你显然不能每次遍历一遍所有待更新深度,利用从下往上合并这个性质,把所有深度塞进一个堆里,深度较小的在前,从下往上匹配所以优先当前深度最小者显然是正确的,满足堆顶和当前深度相同时合并,否则继续向上计算。
到根了当然不能继续计算,把所有节点取出来处理。
实现好难……
#include <bits/stdc++.h> #define break ({break;}) #define continue ({continue;}) #define return(kafu) ({return kafu;}) using namespace std; typedef long long i64; typedef double f16; typedef pair <int, int> pii; const int N = 5e5 + 10; int n, d, dep[N], siz[N], hev[N], dfn[N], dcnt, d2i[N]; unordered_map <int, bool> exc[N]; // 某个深度的点要匹配点的期望深度标记 (?) map <int, vector <int> > ind[2]; vector <int> g[N]; vector <pii> ans; bool used[N]; struct vertex { int d, i; vertex (int _d, int _i) : d (_d), i (_i) {} }; struct cmp_vertex { bool operator () (vertex x, vertex y) { return x.d < y.d; } }; priority_queue <vertex, vector <vertex>, cmp_vertex> vt; #define qry_b(v,w,p) auto v = w.upper_bound (p) int oe (int c) { return (c & 1); } void DFS (int u, int f) { dep[u] = dep[f] + 1, siz[u] = 1; for (int v : g[u]) { if (v == f) continue; DFS (v, u), siz[u] += siz[v]; if (siz[v] > siz[hev[u]]) hev[u] = v; } } void reset (int u) { for (int i = dfn[u]; i <= dfn[u] + siz[u] - 1; i ++) exc[dep[d2i[i]]].clear (); ind[0].clear (), ind[1].clear (); while (vt.size ()) vt.pop (); } void find (int u, int c, int ad = -1) // 在 u 的子树里给一个深度为 c 的点找到匹配点 (?) { qry_b (v, ind[!oe (c)], dep[u] * 2 + d - c); if (v != ind[!oe (c)].begin ()) { v --; int dv = v -> first; if (!exc[c].count (dv)) vt.emplace ((c + dv - d) / 2, c), exc[c][dv] = true; } if (~ad) ind[oe (c)][c].push_back (ad); } void process (int u) { while (vt.size ()) { if (vt.top ().d < dep[u] && u != 1) return; // 堆顶不足 dep 且不为根返回 int ed = vt.top ().i, od = vt.top ().d * 2 + d - ed; vt.pop (); exc[ed].erase (od); if (ed & 1) swap (ed, od); // 分奇偶 if (!ind[0].count (ed) || !ind[1].count (od)) continue; int e = ind[0][ed].back (), o = ind[1][od].back (); // 取出对应点 ind[0][ed].pop_back (), ind[1][od].pop_back (); if (ind[0][ed].empty ()) ind[0].erase (ed); // 为空时删除该深度 if (ind[1][od].empty ()) ind[1].erase (od); ans.emplace_back (e, o), used[e] = used[o] = true; qry_b (en, ind[0], ed); qry_b (on, ind[1], od); if (en != ind[0].begin ()) en --, find (u, en -> first); if (on != ind[1].begin ()) on --, find (u, on -> first); } } void DSU (int u, int f) { dfn[u] = ++ dcnt, d2i[dcnt] = u; for (int v : g[u]) if (v != f && v != hev[u]) DSU (v, u), reset (v); if (hev[u]) DSU (hev[u], u); find (u, dep[u], u); for (int v : g[u]) if (v != f && v != hev[u]) for (int i = dfn[v]; i <= dfn[v] + siz[v] - 1; i ++) if (!used[d2i[i]]) find (u, dep[d2i[i]], d2i[i]); process (u); } int main () { cin >> n >> d; for (int i = 1, u, v; i < n; i ++) cin >> u >> v, g[u].push_back (v), g[v].push_back (u); DFS (1, 0), DSU (1, 0), cout << ans.size () << '\n'; for (pii i : ans) cout << i.first << ' ' << i.second << '\n'; return 0; }
- 1
信息
- ID
- 9582
- 时间
- 2000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者