洛谷题单指南-图论之树-P3038 [USACO11DEC] Grass Planting G
原题链接: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] > sz[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,对于路径权值的更新要排除LCA
add(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;
}
浙公网安备 33010602011771号