这题大厉害了かふ。
题意简述:
给定一棵
一只青蛙可以跳跃一次到与其所在点相邻的点并改变颜色,若是绿色青蛙则变成棕色,反之变成绿色。一只青蛙最多跳跃
如果两只在同一个点的青蛙颜色不同则可以组成一对合唱队,每只青蛙最多加入一对合唱队。
现在请求出能组成的最大合唱队数以及每一对是哪两只青蛙组成的。
定义
所有点上初始都有一只青蛙,我们有贪心:若某两点
证明形如,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;
}