序列/树上差分小结 By cellur925
首先我们需要注意一下的是,差分比较适用于修改比较多而查询比较少的情况。
一、序列上差分
借教室 这是一道二分答案,在check函数中用到差分技巧的一道题,譬如说我们要把一个序列中[l,r]区间都加上一个权值,我们可以把在 l 处加上这个值,在r+1处减去这个值,再对记录权值的数组求前缀和,那么我们就可以得到这个真正的权值数组。
题解 在链接里,代码就不放了=w=。
二、树上差分
树上差分可以分为在点权上的情况和 在边权上的情况。
1: 点权 :
比如在树上把 从u到v的路径的某个权值都加上一个数,设这个权值数组val,那么我们需要在val[u],val[v]上加上这个值,并在val[lca(u,v)]上减去这个值,并在val[f[lca(u,v][0]]上减去这个值。
理论如斯。
这个算法就经常用于统计树上路径某点/某边的经过次数。
例题1 [USACO15DEC]最大流Max Flow(不是网络流题目啦==)
给出若干路径,求被经过此处最多的点的经过次数。
Sol:树上倍增求LCA+树上差分记录+最后O(n)扫一遍更新。
总复杂度O(nlogn预处理+klogn求LCA+n更新)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 #include<queue> 5 #define maxn 500900 6 7 using namespace std; 8 9 int n,k,t,ans,tot; 10 int head[maxn],d[maxn],val[maxn],f[maxn][20]; 11 struct node{ 12 int to,next; 13 }edge[maxn*2]; 14 15 void add(int x,int y) 16 { 17 edge[++tot].next=head[x]; 18 head[x]=tot; 19 edge[tot].to=y; 20 } 21 22 void init_LCA() 23 { 24 queue<int>q; 25 q.push(1); 26 d[1]=1; 27 while(!q.empty()) 28 { 29 int u=q.front(); 30 q.pop(); 31 for(int i=head[u];i;i=edge[i].next) 32 { 33 int v=edge[i].to; 34 if(d[v]) continue; 35 d[v]=d[u]+1; 36 f[v][0]=u; 37 for(int j=1;j<=t;j++) 38 f[v][j]=f[f[v][j-1]][j-1]; 39 q.push(v); 40 } 41 } 42 } 43 44 int LCA(int x,int y) 45 { 46 if(d[x]>d[y]) swap(x,y); 47 for(int i=t;i>=0;i--) 48 if(d[f[y][i]]>=d[x]) y=f[y][i]; 49 if(x==y) return x; 50 for(int i=t;i>=0;i--) 51 if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; 52 return f[x][0]; 53 } 54 55 void review(int u,int fa) 56 { 57 for(int i=head[u];i;i=edge[i].next) 58 { 59 int v=edge[i].to; 60 if(v==fa) continue; 61 review(v,u); 62 val[u]+=val[v]; 63 } 64 ans=max(ans,val[u]); 65 } 66 67 int main() 68 { 69 scanf("%d%d",&n,&k); 70 t=log2(n)+1; 71 for(int i=1;i<=n-1;i++) 72 { 73 int x=0,y=0; 74 scanf("%d%d",&x,&y); 75 add(x,y),add(y,x); 76 } 77 init_LCA(); 78 while(k--) 79 { 80 int x=0,y=0; 81 scanf("%d%d",&x,&y); 82 int fa=LCA(x,y); 83 // printf("%d\n",fa); 84 val[x]++;val[y]++; 85 val[fa]--;val[f[fa][0]]--; 86 } 87 review(1,0); 88 printf("%d",ans); 89 return 0; 90 }
例题2 [JLOI2014]松鼠的新家
也是求点经过次数的=w=,稍有变动,需要在最后从第2个到达点到最后一个到达点的权值都减。(题意使然)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 #include<queue> 5 #define maxn 300090 6 7 using namespace std; 8 9 int n,tot,t; 10 int seq[maxn],head[maxn],val[maxn],d[maxn],f[maxn][20]; 11 struct node{ 12 int to,next; 13 }edge[maxn*2]; 14 15 void add(int x,int y) 16 { 17 edge[++tot].to=y; 18 edge[tot].next=head[x]; 19 head[x]=tot; 20 } 21 22 void LCA_prework() 23 { 24 queue<int>q; 25 q.push(1),d[1]=1; 26 while(!q.empty()) 27 { 28 int u=q.front();q.pop(); 29 for(int i=head[u];i;i=edge[i].next) 30 { 31 int v=edge[i].to; 32 if(d[v]) continue; 33 d[v]=d[u]+1; 34 f[v][0]=u; 35 for(int j=1;j<=t;j++) 36 f[v][j]=f[f[v][j-1]][j-1]; 37 q.push(v); 38 } 39 } 40 } 41 42 int LCA(int x,int y) 43 { 44 if(d[x]>d[y]) swap(x,y); 45 for(int i=t;i>=0;i--) 46 if(d[f[y][i]]>=d[x]) y=f[y][i]; 47 if(x==y) return x; 48 for(int i=t;i>=0;i--) 49 if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; 50 return f[x][0]; 51 } 52 53 void review(int u,int fa) 54 { 55 for(int i=head[u];i;i=edge[i].next) 56 { 57 int v=edge[i].to; 58 if(v==fa) continue; 59 review(v,u); 60 val[u]+=val[v]; 61 } 62 } 63 64 int main() 65 { 66 scanf("%d",&n); 67 t=log2(n)+1; 68 for(int i=1;i<=n;i++) scanf("%d",&seq[i]); 69 for(int i=1;i<=n-1;i++) 70 { 71 int x=0,y=0; 72 scanf("%d%d",&x,&y); 73 add(x,y),add(y,x); 74 } 75 LCA_prework(); 76 for(int i=1;i<=n-1;i++) 77 { 78 int fa=LCA(seq[i],seq[i+1]); 79 val[seq[i]]++;val[seq[i+1]]++; 80 val[fa]--;val[f[fa][0]]--; 81 } 82 review(1,0); 83 for(int i=2;i<=n;i++) val[seq[i]]--; 84 for(int i=1;i<=n;i++) printf("%d\n",val[i]); 85 return 0; 86 }
(话说省选出板子题真的好么==)
2: 边权:
给你一棵树,有n次修改操作,每次把u..v的路径权值加x,最后问从x..y的路径权值和。
例如有一次操作是把红点(u)到绿点(v)之间的路径全部加x。那么我就标记dlt[u]+=x,dlt[v]+=x。然后我们要在lca(u,v)处标记dlt[lca(u,v)]-=2x。这样就使得加x的效果只局限在u..v,不会向lca(u,v)的爸爸蔓延。
上面的话引用自@Sagittariusdalao。
例题:NOIp2015运输计划
val[x]就是x与它的父节点之间的“树边”被覆盖的次数
链接中有详细的题解==