原题链接:https://www.luogu.com.cn/problem/P3038
题意解读:两种操作,对树上路径上的所有边权值加1,查询某一条边的值。
解题思路:
重链剖分既可以对点进行维护,也可以对边进行维护,区别在于边比点少一个,如果边u->v,显然变权值值应该归到v,因为u不能保证边是唯一。
题目要求对路径上所有边加1,可以通过重链剖分转换成对一系列区间进行加1操作,查询只需对某一条边,因此是一个区间更新、单点查询问题,用线段树维护一个dfs序的差分数组即可,区间修改转换成差分数组的单点修改,单点查询转换成前缀和查询。
需要注意的是,在对剖分后的重链区间进行修改时,会会涵盖u-v路径上所有点,而对于边来说,不应该包含lca(u,v)这个点对应的边,因此要在区间操作是排除掉对lca(u,v)这个点的修改。
重链剖分中,lca很容易求得,u、v不断沿着重链往上跳的过程中,当两个点所在重链顶端重合时,u、v深度较小的节点即lca,修改时跳过lca即可。
100分代码:
#include <bits/stdc++.h>
using namespace std;const int N = 100005;vector<int> g[N];
int tr[N]; //树状数组,维护树的dfn序节点的值,用u->v的v节点值代表边
int depth[N], fa[N], sz[N], son[N], dfn[N], cnt, rk[N], top[N]; //重链剖分相关数据
int n, m;int lowbit(int x)
{return x & -x;
}void add(int x, int val)
{for(int i = x ; i <= n; i += lowbit(i)) //一共n-1条边tr[i] += val;
}int sum(int x)
{int res = 0;for(int i = x; i; i -= lowbit(i))res += tr[i];return res;
}void dfs1(int u, int p, int d)
{sz[u] = 1;depth[u] = d;fa[u] = p;for(auto v : g[u]){if(v == p) continue;dfs1(v, u, d + 1);sz[u] += sz[v];if(sz[v] > son[u]) son[u] = v;}
}void dfs2(int u, int t)
{top[u] = t;dfn[u] = ++cnt;rk[cnt] = u;if(son[u]) dfs2(son[u], t);for(auto v : g[u]){if(v == fa[u] || v == son[u]) continue;dfs2(v, v);}
}void updatePath(int u, int v, int val)
{while(top[u] != top[v]){if(depth[top[u]] < depth[top[v]]) swap(u, v);add(dfn[top[u]], 1);add(dfn[u] + 1, -1);u = fa[top[u]];}if(depth[u] > depth[v]) swap(u, v);//u就是LCA,对于路径权值的更新要排除LCAadd(dfn[u] + 1, 1);add(dfn[v] + 1, -1);
}int queryEdge(int u, int v)
{if(depth[u] < depth[v]) swap(u, v);return sum(dfn[u]);
}int main()
{cin >> n >> m;int u, v;for(int i = 1; i < n; i++){cin >> u >> v;g[u].push_back(v);g[v].push_back(u);}dfs1(1, 0, 0);dfs2(1, 1);char op;while(m--){cin >> op >> u >> v;if(op == 'P') updatePath(u, v, 1);else cout << queryEdge(u, v) << endl;}return 0;
}