![]()

染色:给定一颗黑白树,请快速执行①将给定节点颜色翻转②求给定节点到最近的白色节点距离。
4.6划分、解决、合并:分治法
树上的分治法
重心分解后,对每个子树root维护一个优先队列que,维护子树中各白点到root的距离。这样染黑色时可以快速从que中找到仍然是白色的第一个点。如果不用que,只用一个元素Path的话,染白色固然没问题,染黑色就麻烦了。
查询x时,遍历x所属子树root,按如下方式更新答案
ans = min(ans, dist(x, root) + dist_white(root)); // x到根节点+根节点到白点
至于如何求dist,可以参考POJ 1986 Distance Queries 题解《挑战程序设计竞赛》,用LCA快速实现。
另外需注意查询次数可能非常多,需要预处理出所有的重心,而不要来一个查询求一次重心。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define MAX_V 100004
#define MAX_LOG_V 16
#define INF 0x3f3f3f3f
vector<int> G[MAX_V];
// 重心分割
int subtree_size[MAX_V], centroid[MAX_V]; // -1未初始化,-2没有上级子树
void compute_subtree_size(int v, int p)
{
subtree_size[v] = 1;
for (int i = 0; i < G[v].size(); ++i)
{
int to = G[v][i];
if (to != p && centroid[to] == -1)
{
compute_subtree_size(to, v);
subtree_size[v] += subtree_size[to];
}
}
}
void search_centroid(int v, int p, int n, int prev_centroid)
{
for (int i = 0; i < G[v].size(); ++i)
{
int to = G[v][i];
if (to != p && centroid[to] == -1 && 2 * subtree_size[to] > n)
{
search_centroid(to, v, n, prev_centroid);
return;
}
}
centroid[v] = prev_centroid;
for (int i = 0; i < G[v].size(); ++i)
{
int to = G[v][i];
if (centroid[to] == -1)
{
compute_subtree_size(to, -1);
search_centroid(to, v, subtree_size[to], v);
}
}
}
void init_centroid(int root)
{
memset(centroid, -1, sizeof centroid);
compute_subtree_size(root, -1);
search_centroid(root, -1, subtree_size[root], -2);
}
struct Path
{
int to, dist;
inline bool operator<(const Path &other) const
{
return dist > other.dist;
}
};
// LCA
int parent[MAX_LOG_V][MAX_V]; // 向上走2^k步所到的节点(超过根时记为-1)
int depth[MAX_V]; // 节点的深度
void dfs(int v, int p, int d)
{
parent[0][v] = p;
depth[v] = d;
for (int i = 0; i < G[v].size(); i++)
{
if (G[v][i] != p)
dfs(G[v][i], v, d + 1);
}
}
// 预处理
void init_lca(int V)
{
dfs(0, -1, 0);
// 预处理出parent
for (int k = 0; k + 1 < MAX_LOG_V; k++)
{
for (int v = 0; v < V; v++)
{
if (parent[k][v] < 0)
parent[k + 1][v] = -1;
else
{
parent[k + 1][v] = parent[k][parent[k][v]];
}
}
}
}
// 计算u和v的LCA
int lca(int u, int v)
{
// 让u和v向上走到同一深度
if (depth[u] > depth[v])
swap(u, v);
for (int k = 0; k < MAX_LOG_V; k++)
{
if ((depth[v] - depth[u]) >> k & 1)
{
v = parent[k][v];
}
}
if (u == v)
return u;
// 利用二分搜索计算LCA
for (int k = MAX_LOG_V - 1; k >= 0; k--)
{
if (parent[k][u] != parent[k][v])
{
u = parent[k][u];
v = parent[k][v];
}
}
return parent[0][u];
}
int dist(int u, int v)
{
return depth[u] + depth[v] - 2 * depth[lca(u, v)];
}
priority_queue<Path> que[MAX_V]; // que[centroid] := 中心centroid离子树中某个白点的路径
bool white[MAX_V];
int dist_white(int x)
{
priority_queue<Path> &q = que[x];
while (!q.empty())
{
Path cur = q.top();
if (!white[cur.to])
{
q.pop();
}
else
{
return cur.dist; // 最近的白点
}
}
return INF;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("err.txt", "w", stderr);
#endif
int N, Q;
scanf("%d", &N);
for (int i = 1, u, v; i < N; ++i)
{
scanf("%d %d", &u, &v);
--u, --v;
G[u].push_back(v);
G[v].push_back(u);
}
init_centroid(1);
init_lca(N);
scanf("%d", &Q);
int op, x;
while (Q--)
{
scanf("%d %d", &op, &x);
--x;
if (op == 0)
{
white[x] = !white[x];
if (white[x])
{
int root = x;
while (root != -2)
{
que[root].push(Path{x, dist(x, root)}); // 记录白点
root = centroid[root];
}
}
}
else
{
int ans = INF, root = x;
while (root != -2)
{
ans = min(ans, dist(x, root) + dist_white(root)); // x到根节点+根节点到白点
root = centroid[root];
}
printf("%d\n", ans == INF ? -1 : ans);
}
}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stderr);
#endif
return 0;
}

知识共享署名-非商业性使用-相同方式共享:码农场 » SPOJ QTREE5 Query on a tree V 题解《挑战程序设计竞赛》
码农场