题解 CF191C Fools and Roads
题目简述
给定一棵 \(n\) 个节点的树,最开始所有边的边权为 \(0\) ,有 \(m\) 次操作,每次操作会给定两个点,把这两个点的路径上的所有边的权值 \(+1\) ,操作完成后,输出所有的边的权值。
\(2 \leq n \leq 10^5 ,0 \leq m \leq 10^5\) 。
保证每次操作的两个点不是同一个点。
Solution
下面用 \(lca(u,v)\) 表示 \(u,v\) 的最近公共祖先, \(fa_x\) 表示 \(x\) 的父节点。
看到链 \(+1\) ,又是统一输出答案,那肯定是树上差分。
首先把边权转换到点上面去:因为每个点只有一个父亲,所以每个点到父亲的边是唯一确定的,因此我们可以把每个点到父亲的这条边的权值记录到这个点上去。
然后就变成了把一条路径中的除去最高点的所有点的点权 \(+1\) 。
简单说一下树上差分是怎么做的:我们用一个 \(w_i\) 来表示 \(i\) 到根路径上的所有点的权值都应该要加上 \(w_i\) ,最后一个点真正的权值就是这个点子树的所有节点的权值和(这个点的子树里的所有点到根的路径一定经过了当前这个点,因为当前这个点是子树的根节点)。
然后把把 \(u\) 到 \(v\) 路径上的所有点权值 \(+1\) 拆开,变成让 \(u\) 到根所有点的权值 \(+1\) , \(v\) 到根所有点的权值 \(+1\) ,然后 \(w_{lca(u,v)} \gets w_{lca(u,v)} - 2\) ,也就是说, \(lca(u,v)\) 到根上的所有点的权值都被多加了两次,我们要把这两次减掉
注意:这里的修改的点并不包括 \(w_{lca(u,v)}\) ,因为这里是边权,而 \(lca(u,v)\) 到 \(fa_{lca(u,v)}\) 的边不在 \(u\to v\) 上,所以不用加。
如果题目本身给出的就是点权,那么 \(w_{lca(u,v)}\) 本来应该加一次,现在加了两次,应该减掉一次,也就是 \(w_{lca(u,v)} \gets w_{lca(u,v)} - 1\) 然后 \(fa_{lca(u,v)}\) 到根的所有点的权值都被多加了两次 ,应该让 \(w_{fa_{lca(u,v)}} \gets w_{fa_{lca(u,v)}} - 2\) 。对边进行修改和对点进行修改是不一样的。如果不能理解可以画出图来,就能明白了。
这样的话,复杂度为 \(\mathcal{O}(m \log n)\) (假设使用的是 \(\mathcal{O}(n)-\mathcal{O}(\log n)\) 的 \(lca\) )。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 1e5 + 5 ,M = 2e5 + 5;
struct Edge {
int to ,next;
Edge (int to = 0 ,int next = 0) :
to(to) ,next(next) {}
}G[M]; int head[N] ,cnt;
inline void add(int u ,int v) {
G[++cnt] = Edge(v ,head[u]); head[u] = cnt;
G[++cnt] = Edge(u ,head[v]); head[v] = cnt;
}
int fa[N] ,son[N] ,siz[N] ,dep[N] ,top[N] ,a[N];
inline void dfs1(int now) {
dep[now] = dep[fa[now]] + 1;
siz[now] = 1;
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now]) continue;
fa[v] = now;
dfs1(v);
siz[now] += siz[v];
if (siz[v] > siz[son[now]]) son[now] = v;
}
}
inline void dfs2(int now ,int t) {
top[now] = t;
if (!son[now]) return ;
dfs2(son[now] ,t);
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now] || v == son[now]) continue;
dfs2(v ,v);
}
}
inline void dfs3(int now) {
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now]) continue;
dfs3(v);
a[now] += a[v];
}
}
inline int LCA(int x ,int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x ,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
struct edge {
int u ,v;
edge (int u = 0 ,int v = 0) : u(u) ,v(v) {}
}e[N];
int n ,m;
signed main() {
n = read();
for (int i = 1; i <= n - 1; i++) {
e[i].u = read(); e[i].v = read();
add(e[i].u ,e[i].v);
}
dfs1(1); dfs2(1 ,1);
m = read();
while (m--) {
int x = read() ,y = read();
int lca = LCA(x ,y);
a[x]++; a[y]++; a[lca] -= 2;
}
dfs3(1); //统计和
for (int i = 1; i <= n - 1; i++) {
int u = e[i].u ,v = e[i].v;
printf("%d%c" ,dep[u] < dep[v] ? a[v] : a[u] ," \n"[i == n - 1]);
//这条边的权值存在子节点上,子节点的深度更大,所以是取深度大的点的权值。
}
return 0;
}

题解 CF191C Fools and Roads
浙公网安备 33010602011771号