1<n,m<=300000
对于一个人从 u 走到 v,则路径 u->LCA(u,v)->v 上的所有点经过的人数增加 1,路径上所有边上经过的人数也或增加 1。前面我们已经介绍这类问题提在序列上如何用差分思想来优化算法,但这里是树,能否利用这个思想呢?答案是肯定的!
1、树上差分数组
设差分数组:(下面所提到的权值表示经过人数)
A[i] 表示结点 i 的权值与其“所有儿子结点权值和”的差。
B[i] 表示边 fa[i]->i 的权值与所有i->son[i]的边权值和的差。
那么我们现在来看一次修改:一个人从 u 走到 v,差分数组该如何修改:
该人走过的路径为 u->z->v,那么
对于点权差分数组有:
A[u]++, A[v]++,
A[z]--, A[fa[z]]--;
对于边权差分数组有:
B[u]++, B[v]++;
B[z]=B[z]-2;
2、差分数组前缀和
设点权前缀和为 SA[i],边权前缀和为 SB[i],编号为 id的边权为 sum[id]
1)、计算经过点 i 的人数:
????[??] = ????[??1] + ????[??2] + ? + ????[????] + ??[??]
2)、计算经过边 fa[i]->i 的人数:
????[??] = ????[??1] + ????[??2] + ? + ????[????] + ??[??]
设这条边的编号为 id,则??????[????] = ????[i]
算法时间复杂度分析:
对于每一个从 u 到 v 旅行的人,需要用??(??????2 ??)的时间复杂度计算 LCA(u,v),然后用 O(1)的时间
复杂度维护差分数组,所以时间总的时间复杂度为??(?? × ??????2 ??)
某些实现细节见代码:

#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define maxn 300005 #define maxm 600005 #define id(x) ((x+1)>>1) using namespace std; int fir[maxn], ne[maxm], to[maxm], id[maxm], np=0; void add(int x,int y,int z){ ne[++np] = fir[x]; fir[x] = np; to[np] = y; id[np] = z; //LCA若用倍增法在线算用宏定义那个id()算边的ID每次需要时计算有个点会被卡 } int dep[maxn], fa[maxn][20]; void dfs(int u,int f,int d){ dep[u] = d; fa[u][0] = f; for(int k = 1; k <= 18; k++){ int j = fa[u][k-1]; fa[u][k] = fa[j][k-1]; } for(int i = fir[u]; i; i=ne[i]){ int v = to[i]; if(v != f) dfs(v, u, d+1); } } int LCA(int x,int y){ if(dep[x] < dep[y]) swap(x, y); int j = dep[x] - dep[y]; for(int k = 18; k >= 0; k--) if((1<<k)&j) x = fa[x][k]; if(x == y) return x; for(int k = 18; k >= 0; k--) if(fa[x][k] != fa[y][k]) x = fa[x][k], y = fa[y][k]; return fa[x][0]; } int n,m; void data_in(){ scanf("%d%d", &n, &m); for(int i=1, x, y; i<n; i++){ scanf("%d%d", &x, &y); add(x, y, i); add(y, x, i); } } int A[maxn], B[maxn]; int SA[maxn], SB[maxn]; int sum[maxn]; void _DFS(int u,int f){ SA[u] = A[u], SB[u] = B[u]; for(int i=fir[u];i;i=ne[i]){ int v = to[i]; if(v == f) continue; _DFS(v, u); SA[u] += SA[v]; SB[u] += SB[v]; sum[id[i]] = SB[v];//!!! } } void solve(){ dfs(1, 0, 0); int u, v, z; while(m--){ scanf("%d%d",&u,&v); z = LCA(u, v); A[u]++; A[v]++; A[z]--; A[fa[z][0]]--; B[u]++; B[v]++; B[z]-=2; } _DFS(1, 0); for(int i=1;i<n;i++) printf("%d ",SA[i]); printf("%d\n", SA[n]); //cqyz oj自带输出坑 for(int i=1;i<n-1;i++) printf("%d ",sum[i]); printf("%d",sum[n-1]); } int main(){ data_in(); solve(); return 0; }
对于某数据卡在线的倍增算法,可以考虑用离线的tanjar算法,时间O(n+2*m)。
(以下代码部分WA,如果有大佬看出问题请在评论区找我orz)

#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<vector> #include<algorithm> #define maxn 300005 #define maxm 600005 #define id(x) ((x+1)>>1) using namespace std; int fir[maxn], ne[maxm], to[maxm], np=0; void add(int x,int y){ ne[++np] = fir[x]; fir[x] = np; to[np] = y; } vector<int> que[maxn]; int A[maxn], B[maxn]; int SA[maxn], SB[maxn]; int sum[maxn]; int vis[maxn], fa[maxn]; int bcj[maxn]; int Find(int x){return x == bcj[x] ? x : bcj[x] = Find(bcj[x]);} int n,m; void data_in(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) bcj[i] = i, vis[i] = 0; for(int i=1, x, y; i<n; i++){ scanf("%d%d", &x, &y); add(x, y); add(y, x); } while(m--){ int u, v; scanf("%d%d",&u,&v); que[u].push_back(v); que[v].push_back(u); } } void tanjar(int u,int f){ vis[u] = 1; fa[u] = f; for(int p=0, siz=que[u].size(), lca; p<siz; p++){ int v = que[u][p]; if(vis[v]){ lca = Find(v); A[u]++; A[v]++; A[lca]--; A[fa[lca]]--; B[u]++; B[v]++; B[lca]-=2; } } for(int i=fir[u];i;i=ne[i]){ int v=to[i]; if(v == f) continue; tanjar(v, u); bcj[v] = u; } } void _DFS(int u,int f){ SA[u] = A[u], SB[u] = B[u]; for(int i=fir[u];i;i=ne[i]){ int v = to[i]; if(v == f) continue; _DFS(v, u); SA[u] += SA[v]; SB[u] += SB[v]; sum[id(i)] = SB[v]; } } void solve(){ tanjar(1, 0); _DFS(1, 0); for(int i=1;i<n;i++) printf("%d ",SA[i]); printf("%d\n", SA[n]); for(int i=1;i<n-1;i++) printf("%d ",sum[i]); printf("%d",sum[n-1]); } int main(){ data_in(); solve(); return 0; }