树上差分--树链剖分的前置知识 P3128 [USACO15DEC]Max Flow P,P2680 运输计划

树上差分

树上差分,就是利用差分的性质,对路径上的重要节点进行修改(而不是暴力全改),作为其差分数组的值,最后在求值时,利用\(dfs\)遍历求出差分数组的前缀和,就可以达到降低复杂度的目的,可以应用于求对树上的一段路径进行操作,并询问某个点或某条边被经过的次数

点差分

设将两点\(u\),\(v\)之间路径上的所有点权增加\(x\)\(res=LCA(u,v)\),\(res\)的父亲节点为\(tmp\),则操作如下:

  siz[u]+=x,siz[v]+=x,siz[res]-=x,siz[tmp]-=x;

这样,只要\(dfs\)一遍,遍历时统计以每个节点为根的树的节点的权值和,就是当前节点的最终权值

例题: 点差分板子

code:

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int read(){
	int x = 1,a = 0 ;char ch = getchar();
	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
	return x*a;	
}
const int maxn = 1e5+10;
int n,k;
struct node{
	int to,nxt;
}ed[maxn*2];
int head[maxn*2],tot;
void add(int u,int to){
	ed[++tot].to = to;
	ed[tot].nxt = head[u];
	head[u] = tot;
}
int f[maxn][30],dep[maxn],siz[maxn];
void dfs(int x,int fa){
	dep[x] = dep[fa] + 1;
	for (int i = 1;i <= 22;i++) f[x][i] = f[f[x][i-1]][i-1];
	for (int i = head[x];i;i = ed[i].nxt){
		int to = ed[i].to;
		if (to == fa) continue;
		f[to][0] = x;
		dfs(to,x);
	}
}
int lca(int x,int y){
	if (dep[x] > dep[y]) swap(x,y);
	for (int i = 22;i >= 0;i--){
		if (dep[f[y][i]] >= dep[x]) y = f[y][i];
	}
	if (x == y) return x;
	for (int i = 22;i >= 0;i--){
		if (f[y][i] != f[x][i]) x = f[x][i],y = f[y][i];
	}
	return f[x][0];
}
void dfs2(int x,int fa){
	for (int i = head[x];i;i = ed[i].nxt){
		int to = ed[i].to;
		if (to == fa) continue;
		dfs2(to,x);
		siz[x] += siz[to];
	}
}
int main(){
	n = read(),k = read();
	for (int i = 1;i <= n-1;i++){
	 	int x = read(),y = read();
		add(x,y);add(y,x);
	}
	dfs(1,0); 
	for (int i = 1;i <= k;i++){
		int x = read(),y = read();
		int res = lca(x,y),tmp = f[res][0];
		siz[x]++,siz[y]++,siz[res]--,siz[tmp]--;
	}
	dfs2(1,0);
	int num = 0;
	for (int i = 1;i <= n;i++) num = max(num,siz[i]);
	printf("%d\n",num);
	return 0;
}

边差分

设将两点𝑢,𝑣之间路径上的所有边权增加\(x\)\(res=LCA(u,v)\),以每条边两端深度较大的节点存储该边的差分数组,则操作如下:

  siz[u]+=x,siz[v]+=x,siz[res]-=2*x

同样地,只要\(dfs\)一遍,遍历时统计以每个节点为根的树的节点的权值和,就是当前节点到父亲节点的边的最终权值了!

例题:

problem: 问题大概可以概括为给定\(m\)条路径,将路径里的一条边清零,使路径长度的最大值最小,求这个最小值

solution: 假这个最小值为\(x\),那么\(m\)条路径中所有长度比\(x\)大的路径,都会有同一条路径被删去(注意删去的是同一条路径)。那么这个\(x\)如何才能合法呢,找所有比\(x\)大的路径\(k\)条,做差分,求出每条路径的经过次数,如果恰好经过\(k\)次,就说明可以删去它,看删去他后最长的那条路径是否\(\leq x\)就可以判断该答案合不合法,二分\(x\),如果满足,那么说明比\(x\)大的也能满足,搜左区间,如果不满足,说明比\(x\)小的也不满足,搜右区间

code:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int read(){
	int x = 1,a = 0;char ch = getchar();
	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
	return x*a;
}
const int maxn = 3e5+10;
int n,m;
struct node{
	int to,nxt,w;
}ed[maxn*2];
struct ask{
	int x,y,dis;
}a[maxn];
int f[maxn][30],dis[maxn],pos[maxn],dep[maxn];
int siz[maxn];
int head[maxn*2],tot;
void add(int u,int to,int w){
	ed[++tot].to = to;
	ed[tot].nxt = head[u];
	ed[tot].w = w;
	head[u] = tot;
}
void dfs1(int x,int fa){
	dep[x] = dep[fa] + 1;
	for (int i = 1;i <= 22;i++) f[x][i] = f[f[x][i-1]][i-1];
	for (int i = head[x];i;i = ed[i].nxt){
		int to = ed[i].to;
		if (to == fa) continue;
		dis[to] = dis[x] + ed[i].w;f[to][0] = x;pos[to] = i;
		dfs1(to,x);
	}
}
int lca(int x,int y){
	if (dep[x] > dep[y]) swap(x,y);
	for (int i = 22;i >= 0;i--){
		if (dep[f[y][i]] >= dep[x]) y = f[y][i];
	}
	if (x == y) return x;
	for (int i = 22;i >= 0;i--){
		if (f[y][i] != f[x][i]) x = f[x][i],y = f[y][i];
	}
	return f[x][0];
}
bool cmp(ask x,ask y){
	return x.dis < y.dis;
}
void dfs2(int x,int fa){
	for (int i = head[x];i;i = ed[i].nxt){
		int to = ed[i].to;
		if (to == fa) continue;
		dfs2(to,x);
		siz[x] += siz[to];
	}
}
bool check(int s){
	int num = 0;
    for(int i = 1;i <= m;i++) {
        if(a[i].dis <= s) continue;
        int tmp = lca(a[i].x,a[i].y);num++;
        siz[tmp] -= 2,siz[a[i].x]++,siz[a[i].y]++;
    }
    dfs2(1,0);
    int maxx = 0;
    for(int i = 1;i <= n;i++) {
        if(siz[i] == num) {
            maxx = max(maxx,ed[pos[i]].w);
        }
    }
    memset(siz,0,sizeof(siz));
    return a[m].dis-maxx <= s;
}
int main(){
	n = read(),m = read();
	for (int i = 1;i <= n-1;i++){
		int x = read(),y = read(),w = read();
		add(x,y,w),add(y,x,w);
	}
	dfs1(1,0);
	for (int i = 1;i <= m;i++){
		a[i].x = read(),a[i].y = read();
		int res = lca(a[i].x,a[i].y);
		a[i].dis = dis[a[i].x] + dis[a[i].y] - 2*dis[res];
	}
	sort(a+1,a+m+1,cmp);
	int l = 0,r = 1e9,mid;
	while (l<r){
		mid = (l+r)>>1;
		if (check(mid)) r = mid;
		else l = mid+1;
	}
	printf("%d\n",r);
	return 0;
}
posted @ 2020-11-13 17:31  小又又yyyy  阅读(123)  评论(0编辑  收藏  举报