树上差分
点差分
- cf[maxn] cf[i]记录点i被经过了多少次
当s->t最短路径上每个点都被经过一次,则:
cf[s]++; cf[t]++; cf[lca(s,t)]--; cf[ father[lca(s,t)] ]--;
边差分
- cf[maxn] 记录点i到其父节点的边被经过了多少次
当s->t最短路径上每个边都被经过一次,则:
cf[s]++; cf[t]++; cf[lca(s,t)] -= 2;
核心算法
LCA,可以用倍增法,在线求LCA(s,t),
int depth[maxn];//记录每个结点的深度 根节点深度为0 int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点 //预处理出每个结点的深度和其直接父节点 void dfs(int u, int pre, int d){ fa[u][0] = pre;//向上一个当然是直接父节点 pre depth[u] = d;//深度 for(int i=head[u]; i; i=edge[i].nxt){ int v = edge[i].v; if(v != pre){ //求点到树根的距离 dist[v] = dist[u] + edge[i].w; dfs(v , u, d+1); } } } //倍增预处理出fa数组 void init(){ //有点区间DP的意思 for(int j=0; (1<<(j+1))<n; j++){ for(int i=1; i<=n; i++){ if(fa[i][j] < 0) fa[i][j+1] = -1; else fa[i][j+1] = fa[fa[i][j]][j]; } } } //给定俩个结点在线求其LCA int LCA(int u, int v){ //保证v是较深的点 if(depth[u] > depth[v]) swap(u , v); int temp = depth[v] - depth[u];//深度差 //先把v调到与u等高处 for(int i=0; (1<<i)<=temp; i++){ if((1<<i) & temp) v = fa[v][i]; } if(u==v) return u; //然后两个人比翼双飞 for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){ if(fa[u][i] != fa[v][i]){ u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; }
DFS遍历树得到差分数组,得到cf[i] (这个顶点或者边被经过了多少次)
//遍历差分 void dfs2(int u, int f){ for(int i=head[u]; i; i=edge[i].nxt) { int v = edge[i].v; if(v == f) continue; dfs2(v , u); cf[u] += cf[v]; } }
例题
- 洛谷 P3128 [USACO15DEC]最大流Max Flow
题意:点差分板子题
//LCA 倍增 大家一起跳跳跳 #include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 5e4+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int t,n,k; vector<int> G[maxn]; int s,e,lca; int depth[maxn];//记录每个结点的深度 根节点深度为0 int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点 int ans = -1;//最终答案 int value[maxn];//每个结点的点权 //预处理出每个结点的深度和其直接父节点 void dfs(int u, int pre, int d){ fa[u][0] = pre;//向上一个当然是直接父节点 pre depth[u] = d;//深度 for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(v != pre){ dfs(v , u, d+1); } } } //倍增预处理出fa数组 void init(){ //有点区间DP的意思 for(int j=0; (1<<(j+1))<n; j++){ for(int i=1; i<=n; i++){ if(fa[i][j] < 0) fa[i][j+1] = -1; else fa[i][j+1] = fa[fa[i][j]][j]; } } } //给定俩个结点在线求其LCA int LCA(int u, int v){ //保证v是较深的点 if(depth[u] > depth[v]) swap(u , v); int temp = depth[v] - depth[u];//深度差 //先把v调到与u等高处 for(int i=0; (1<<i)<=temp; i++){ if((1<<i) & temp) v = fa[v][i]; } if(u==v) return u; //然后两个人比翼双飞 for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){ if(fa[u][i] != fa[v][i]){ u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; } //遍历差分 void dfs2(int u, int f){ for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(v == f) continue; dfs2(v , u); value[u] += value[v]; } if(value[u] > ans) ans = value[u]; } int main(){ int u,v; while(sc2(n,k) != EOF){ MS(value , 0); rep(i , 1, n) G[i].clear(); rep(i , 1, n-1){ sc2(u,v); G[u].push_back(v); G[v].push_back(u); } dfs(1 , 0 , 1) ;//初始化LCA init(); for(int i=1; i<=k; i++){ sc2(u , v); value[u]++; value[v]++; int lca = LCA(u,v); value[lca]--; value[fa[lca][0]]--; } dfs2(1 , 0); pfd(ans); } return 0; }
- 洛谷 P2680 运输计划 (边差分)
- 题意: 给定一棵N个结点的带权树,以及K条路径,现在可以使某一个边的权值变为0,使某条路径的总长度变小,问这些路径的最大值的最小值是多少?
- 思路:
- 先找出最长路径mx和最长边me,那么答案ans必然处于[mx-me , mx]之间
- 二分答案ans,什么时候ans是合法的呢?一是ans>=mx,必然合法;而是若有ct条路径均大于ans,想要使得减去某一条边后这ct条路径各自长度都小于ans,则必然是存在一条长度为len的公共边,且len >= mx - ans(这ct条路径中的最长路径减去这条边后路径长不超过ans)
什么时候它才是公共边?那就是对这ct条边进行差分,进行cf操作后跑dfs,然后看是否存在一条边cf[i] = ct && len[i] >= mx - ans
//LCA 倍增 大家一起跳跳跳 #include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 3e5+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int t,n,k; int head[maxn];//head数组 int cnt;//边数 int dist[maxn];//点i到根节点的路径长度 int cf[maxn];//cf[i]表示i到father[i]这条边被经过了多少次 int dis[maxn];//dis[i]表示第i个路径的总长度 int mx;//最长路径长度 int me;//最长边 int ans;//最终结果 struct node{ int v; int nxt; int w; }edge[maxn<<1]; int lca[maxn]; //存第i个路径两端点的LCA int depth[maxn];//记录每个结点的深度 根节点深度为0 int fa[maxn][20];//fa[i][j]表示i结点向上爬 2^j是哪个结点 int l[maxn],r[maxn];//存路径端点 void Init(){ cnt = 1; mx = me = -1; rep(i , 0 , n){ head[i] = 0; dis[i] = 0; } } void addEdge(int u, int v, int w){ edge[cnt].v = v; edge[cnt].w = w; edge[cnt].nxt = head[u]; head[u] = cnt++; } //预处理出每个结点的深度和其直接父节点 void dfs(int u, int pre, int d){ fa[u][0] = pre;//向上一个当然是直接父节点 pre depth[u] = d;//深度 for(int i=head[u]; i; i=edge[i].nxt){ int v = edge[i].v; if(v != pre){ dist[v] = dist[u] + edge[i].w; dfs(v , u, d+1); } } } //倍增预处理出fa数组 void init(){ //有点区间DP的意思 for(int j=0; (1<<(j+1))<n; j++){ for(int i=1; i<=n; i++){ if(fa[i][j] < 0) fa[i][j+1] = -1; else fa[i][j+1] = fa[fa[i][j]][j]; } } } //给定俩个结点在线求其LCA int LCA(int u, int v){ //保证v是较深的点 if(depth[u] > depth[v]) swap(u , v); int temp = depth[v] - depth[u];//深度差 //先把v调到与u等高处 for(int i=0; (1<<i)<=temp; i++){ if((1<<i) & temp) v = fa[v][i]; } if(u==v) return u; //然后两个人比翼双飞 for(int i=(int)(log(1.0*n)/log(2.0)); i >= 0; i--){ if(fa[u][i] != fa[v][i]){ u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; } //遍历差分 void dfs2(int u, int f){ for(int i=head[u]; i; i=edge[i].nxt) { int v = edge[i].v; if(v == f) continue; dfs2(v , u); cf[u] += cf[v]; } } //这条边必须出现了ct次, 并且它的长度不小于len bool dfsCheck(int u, int f, int ct, int len){ for(int i=head[u]; i ; i=edge[i].nxt){ int v = edge[i].v; if(v == f) continue; if(cf[v] == ct && edge[i].w >= len) return 1; if(dfsCheck(v , u, ct , len)) return 1; } return 0;//根节点返回还找不到,那就不存在这样的公共边 } bool judge(int ans){ int ct = 0;//路径长度超过ans的路径个数 MS(cf , 0);//把各点经过的次数清零 if(ans >= mx) return 1;//如果当前答案大于等于最长路径 肯定行 rep(i , 1, k) { //寻找哪些路径长度大于当前ans,找到,求是否存在一条比较长的公共边使得减去后mx小于等于ans if(dis[i] > ans){ ct++; cf[l[i]]++; cf[r[i]]++; cf[lca[i]] -= 2; } } dfs2(1 , 0); //计算在这些超过ans长度的路径中,每条边被经过的次数 return dfsCheck(1 , 0, ct , mx - ans); } int main(){ int u,v,w; while(sc2(n,k) != EOF){ Init(); //建树 rep(i , 1, n-1){ sc2(u,v); sc(w); addEdge(u,v,w); addEdge(v,u,w); me = max(me , w); } dfs(1 , 0 , 1) ;//初始化LCA init(); //初始化LCA //读取路径 rep(i , 1, k){ sc2(u , v); l[i] = u; r[i] = v; lca[i] = LCA(u,v); dis[i] = dist[u] + dist[v] - 2*dist[lca[i]] ;//求该路径的长度 mx = max(mx , dis[i]);//求出最长路径 } int lo = mx - me; int hi = mx + 1; while(lo <= hi){ int mid = (lo + hi) / 2; if(judge(mid)){ hi = mid - 1; ans = mid; } else{ lo = mid + 1; } } pfd(ans); } return 0; }