树上差分

树上差分

首先,什么是差分呢?

差分,又名差分函数或差分运算,差分的结果反映了离散化之间的一种变化,是研究离散数学的一种工具,常用函数差近似导数。(感谢bai度百科)

说人话,在数组中就是后一个数减去前一个数的值搞到一个集合里,这玩意儿就是差分数组。

那么,什么是树上差分呢?

树上差分,顾名思义,就是在树上进行差分,以起到优化复杂度的目的。主要作用是对树上的路径进行修改和查询操作,在修改多、查询少的情况下复杂度比较优秀。当然这类问题也可以通过其他手段解决,但是呢,树上差分十分的简洁,可以避免调很久玄学错误。

先来一道例题banziti

Max Flow P

题目意思大概就是给你一棵树,然后再给你几组起点终点,起点终点的树上距离经过的每一个点都要+1。求最大点权。

怎么用树上差分解决这个问题呢?如果起点x,终点y,就先开一个新的数组a[],然后在a[x]处+1,a[y]处+1,a[LCA(x,y)]处-1,a[fa[LCA(x,y)]]处-1,再将每一个a[p]点(1≤p≤n)更新为它和它儿子的点权之和。这时每一个点的a[]值就是它们的访问次数了。

代码如下:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int SIZE=500009;
 4 
 5 int n,m,ans;
 6 int cnt,tot;
 7 int head[SIZE],nex[SIZE*2],to[SIZE*2],a[SIZE];
 8 int size[SIZE],d[SIZE],fa[SIZE],dfn[SIZE],rev[SIZE],son[SIZE],top[SIZE];
 9 
10 void add(int u,int v){
11     to[++tot]=v;
12     nex[tot]=head[u];head[u]=tot;
13 }
14 
15 void dfs(int x,int f){
16     size[x]=1;fa[x]=f;d[x]=d[f]+1;
17     for(int i=head[x];i;i=nex[i]){
18         int y=to[i];
19         if(y==f) continue;
20         dfs(y,x);
21         size[x]+=size[y];
22         if(size[son[x]]<size[y]) son[x]=y;
23     }
24 }
25 void dfs2(int x){
26     if(son[fa[x]]!=x){top[x]=x;}
27     else{top[x]=top[fa[x]];}
28     dfn[++cnt]=x;
29     rev[x]=cnt;
30     if(son[x]) dfs2(son[x]);
31     for(int i=head[x];i;i=nex[i]){
32         int y=to[i];
33         if(y==fa[x]||y==son[x]) continue;
34         dfs2(y);
35     }
36 }
37 int LCA(int x,int y){
38     if(d[x]<d[y]) swap(x,y);
39     while(top[x]!=top[y]){
40         if(d[top[x]]<d[top[y]]) swap(x,y);
41         x=fa[top[x]];
42     }
43     if(d[x]>d[y]) swap(x,y);
44     return x;
45 } 
46 void dfss(int x,int f){
47     for(int i=head[x];i;i=nex[i]){
48         int y=to[i];
49         if(y==f) continue;
50         dfss(y,x);
51         a[x]+=a[y];
52     }
53 }
54 int main(){
55     d[1]=1;
56     cin>>n>>m;
57     for(int i=1;i<n;i++){
58         int u,v;
59         cin>>u>>v;
60         add(u,v);
61         add(v,u);
62     }
63     dfs(1,0);
64     dfs2(1);
65     for(int i=1;i<=m;i++){
66         int x,y;
67         cin>>x>>y;
68         int kkk=LCA(x,y);
69         a[x]++;a[y]++;
70         a[kkk]--;
71         a[fa[kkk]]--;
72     }
73     dfss(1,0);
74     for(int i=1;i<=n;i++) ans=max(ans,a[i]);
75     cout<<ans;
76     return 0;
77 }

再来一道闇の連鎖

 

每一条附加边都有以下性质,它们都使得原图搞成一个环,而每一条附加边的两个端点总是在图上。

先分类讨论各种情况,如果第一次切断的是不在环上的边,那么我们任意切掉一条附加边就可以完成任务,答案ans+=不在环上的边的数量*M。如果第一次切断的是只连着一条附加边的边,那么我们切掉该附加边就可达成杀死闇の連鎖的目的ans+=只连着一条附加边的边的数量。如果第一次切断的是连着多条附加边的边,那么对不起,没有任何办法,无法杀死闇の連鎖。

所以我的问题转化成了如何求以上数据(不在环上的边的数量,只连着一条附加边的边的数量)。

因为附加边具有最开始描述的(指斜体部分)性质。我们想到了树上差分!每当出现一条附加边,我就将它的两个端点在树上最短路所经过的边的边权+1,处理边权的最常用方式就是将边权下放。访问时如果a[p]=0(2≤p≤n)ans+=M;访问时如果a[p]=1(2≤p≤n)ans+=1。最后输出ans就对了。

接下来是可可爱爱的代码啦~

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int SIZE=200009;
 5 int n,m,ans,tot,cnt;
 6 int head[SIZE],nex[SIZE*2],to[SIZE*2],a[SIZE];
 7 int siz[SIZE],d[SIZE],fa[SIZE],dfn[SIZE],rev[SIZE],son[SIZE],top[SIZE];
 8 
 9 void add(int u,int v){
10     to[++tot]=v;
11     nex[tot]=head[u];head[u]=tot;
12 }
13 
14 void dfs(int x,int f){
15     siz[x]=1;fa[x]=f;d[x]=d[f]+1;
16     for(int i=head[x];i;i=nex[i]){
17         int y=to[i];
18         if(y==f) continue;
19         dfs(y,x);
20         siz[x]+=siz[y];
21         if(siz[son[x]]<siz[y]) son[x]=y;
22     }
23 }
24 void dfs2(int x){
25     if(son[fa[x]]!=x){top[x]=x;}
26     else{top[x]=top[fa[x]];}
27     dfn[++cnt]=x;
28     rev[x]=cnt;
29     if(son[x]) dfs2(son[x]);
30     for(int i=head[x];i;i=nex[i]){
31         int y=to[i];
32         if(y==fa[x]||y==son[x]) continue;
33         dfs2(y);
34     }
35 }
36 int LCA(int x,int y){
37     if(d[x]<d[y]) swap(x,y);
38     while(top[x]!=top[y]){
39         if(d[top[x]]<d[top[y]]) swap(x,y);
40         x=fa[top[x]];
41     }
42     if(d[x]>d[y]) swap(x,y);
43     return x;
44 }
45 
46 void dfss(int x,int f){
47     for(int i=head[x];i;i=nex[i]){
48         int y=to[i];
49         if(y==f) continue;
50         dfss(y,x);
51         a[x]+=a[y];
52     }
53 } 
54 
55 signed main(){
56     cin>>n>>m;
57     for(int i=1;i<n;i++){
58         int u,v;
59         cin>>u>>v;
60         add(u,v);add(v,u);
61     }
62     dfs(1,0);
63     dfs2(1);
64     for(int i=1;i<=m;i++){
65         int x,y,z;
66         cin>>x>>y;
67         z=LCA(x,y);
68         a[x]++;a[y]++;a[z]-=2;
69     }
70     dfss(1,0);
71     for(int i=2;i<=n;i++){
72         if(a[i]==0) ans+=m;
73         else if(a[i]==1) ans++;
74     }
75     cout<<ans;
76     return 0;
77 }

很快啊,就A了道困难题

 


总结一下,单说树上差分这种思想的话,那可真是非常简单啊,可是你做题的时候压根想不到用树上差分啊。aaaaaaaxxxxxxxbbbbbbb

 

 

posted @ 2021-02-03 16:48  001A  阅读(68)  评论(0编辑  收藏  举报