前置知识:树,LCA,前缀和与差分
边差分
这个名字是在网上看到的,不理解为什么要叫这么一个名字,可能是因为它与 树链修改 有关。当然,用于 树链修改 单点查询 非常方便~
看这个图,该图是以点1为根进行DFS的。如果我们要把从3 -> 4这条树链上所有的点统统加上1,可以都转化为对到根节点的树链的操作,我们可以把3 -> 1全加上1,4 -> 1全加上1,发现2多加了1,1多加了2,所以2 - > 1减掉1,1 -> 1减掉1。这并不难想。但是如何实现把某一结点到根节点上所有的点进行权值加减呢?
联想到差分,我们可以分树链差分,对于3 -> 1链上的点,将这条链的开始(节点3)+1,将这条链的末尾(节点0)-1,最后查询时,把这个节点的子节点权值和自身权值相加即为最终权值。
画图太难用了!
看一道例题:[JLOI2014] 松鼠的新家
一道典型的树链修改 单点查询 + LCA题目。需要注意,我们在对每段路径进行操作的时候,前一个路径的尾端点与后一个路径的首端点多放了一个糖果,而且,最后一段路径的右端点是不用放糖果的,所以要统统减掉。
#include<bits/stdc++.h>
using namespace std;
#define min(x,y) (in[x]<in[y])?x:y
const int N = 3e5 + 1;
int n, ord[N], in[N], st[19][N], tot, w[N];
vector<int> G[N];
bitset<N> flag;
inline void dfs1(int k, int fa){
st[0][in[k] = ++tot] = fa;
for(int v : G[k]) if(!in[v]) dfs1(v, k);
}
inline void dfs2(int k){
flag[k] = 1;
for(int v : G[k]) if(!flag[v]) dfs2(v), w[k] += w[v];
}
inline int lca(int a, int b){
if(a == b) return a;
if((a = in[a]) > (b = in[b])) swap(a, b);
int k = __lg(b-a++);
return min(st[k][a], st[k][b-(1<<k)+1]);
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
for(int i=1; i<=n; ++i) cin>>ord[i];
for(int i=1, a, b; i<n; ++i){
cin>>a>>b;
G[a].push_back(b), G[b].push_back(a);
}
dfs1(1, 0);
for(int i=1; i<=__lg(n); ++i)
for(int j=1; j<=n-(1<<i)+1; ++j)
st[i][j] = min(st[i-1][j], st[i-1][j+(1<<i-1)]);
for(int i=1; i<n; ++i){
int a = ord[i], b = ord[i+1];
int fa = lca(a, b);
// printf("lca(%d, %d) = %d\n", a, b, fa);
++w[a], ++w[b], --w[fa], --w[st[0][in[fa]]];
}
dfs2(1);
for(int i=1; i<=n; ++i){
if(i != ord[1]) --w[i];
cout<<w[i]<<'\n';
}
return 0;
}
标签:图论,int,笔记,树链,flag,差分,权值,节点
From: https://www.cnblogs.com/xiaolemc/p/18133193