序列/树上差分小结 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 }
View Code

 

例题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 }
View Code

(话说省选出板子题真的好么==)

 

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与它的父节点之间的“树边”被覆盖的次数

链接中有详细的题解==

 

小结:树上差分和树上倍增还是比较实用的,也比较灵活,需要加强举一反三能力== 

posted @ 2018-09-16 11:28  cellur925&Chemist  阅读(276)  评论(0编辑  收藏  举报