一些题目: (由易到难)
洛谷P3258松鼠的新家
洛谷P2680运输计划
洛谷P1084疫情控制
树上差分是一个很好的技巧. 主要分为下面两种:
一.关于边的差分(如找被所有路径共同覆盖的边)
首先我们除了一般的up,deep等数组以外,多开一个数组:sum。
sum用来记录点的出现次数(具体点说实际上记录的是点到其父亲的边的出现次数, 我们这样处理:
sum[u]++,sum[v]++,sum[LCA(u, v)]-=2。(记住:最后要从所有叶结点把权值向上累加)以一次操作为例,我们来看看效果(可以画一张图)。首先sum[u]++,一直推上去到根,这时候u到root的路径访问次数都+1,tmp[v]++后,v到lca路径加了1,u到lca路径加了1,而lca到根的路径加了2。
这时,我们只需要sum[LCA(u, v)]-=2,推到根,就能把那些多余的路径减掉,达到想要的目的。而这是一次操作,对于很多次操作的话,我们只需要维护sum,而不必每次更新到根,维护好sum最后dfs一遍即可。这时如果sum[i]==次数的话,说明 i 到其父亲的边是被所有路径覆盖的。如图
例题: 运输控制
首先如果我们知道了一个时间, 我们就可以找到那些点对之间不可能在这个时间完成或者可能, 所以我们考虑二分答案。 怎么check了?对于不满足条件的路径, 记一个数量cnt, 还要记录其中最大的路径值mx, 我们要做的就是找一条边它被覆盖了>=cnt次, 并且该条边权是>=mx-mid的, 如果存在那么这个mid就是答案的一种可能. 所以这里涉及了边的覆盖, 就要用上面所讲的知识
AC Code
const int maxn = 3e5 + 5;
int up[maxn][21], sum[maxn];
int deep[maxn], dis[maxn], dd[maxn];
int cnt, head[maxn];
int n, m;
struct node {
int to, next, w;
}e[maxn<<1];
void init() {
Fill(head,-1); cnt = 0; Fill(sum, 0);
Fill(up,0); Fill(deep,0); Fill(dis, 0);
}
void add(int u, int v, int w) {
e[cnt] = node{v, head[u], w};
head[u] = cnt++;
}
void dfs(int u,int fa,int d) {
deep[u] = d + 1;
for(int i = 1 ; i < 20 ; i ++) {
up[u][i] = up[up[u][i-1]][i-1];
}
for(int i = head[u] ; ~i ; i = e[i].next) {