树上差分
Codeforces - 739B Alyona and a tree (二分+树上差分)
题目大意:
给定一颗带权树,树的根是1,树上每个点都有点权,并且还有边权。现在给出“控制”的定义:对一个点u,设v为其子树上的节点,且$dis(u,v)≤val[v]$,则称u控制v。求出每个点控制的点数。
解题分析:
处理出所有点到根的距离$dist$,然后任意两点之间的距离为$dist[v]-dist[u]$($v$为$u$的子树中的节点)。因为在每条链上,$dist$由根向子树深搜的过程中都是递增的,所以可以用二分快速找到能够控制当前节点的$dep$最浅的父亲节点,然后利用树上差分进行高效的个数统计。
#include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T> inline void read(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define REP(i,s,t) for(int i=s;i<=t;i++) #define fi first #define se second #define pb push_back typedef long long ll; const int N = 2e5+5; int n,m,cnt,loc[N]; ll val[N],dist[N],sum[N],dfn[N]; typedef pair<int,ll>P; vector<P>G[N]; //sum[i]表示每个点能够管辖的节点数 void dfs(int u){ dfn[++cnt]=dist[u];loc[cnt]=u; int cur=lower_bound(dfn+1,dfn+1+cnt,dist[u]-val[u])-dfn;//dist[v]-dist[u]<=val[v] ===>dist[v]-val[v]<=dist[u] //找到能够管辖这个点的深度最浅的祖先节点 sum[loc[cur-1]]--; for(auto &e:G[u]){ int v=e.fi;ll cost=e.se; dist[v]=dist[u]+cost; dfs(v); sum[u]+=sum[v]+1; } --cnt; //将这个链分支中的点全部删除 } int main(){ read(n); REP(i,1,n)read(val[i]); REP(i,2,n){ int u,w;read(u);read(w); G[u].pb(P(i,(ll)w)); } dfs(1); REP(i,1,n)printf("%lld ",sum[i]); }
BZOJ - 4326 NOIP2015 运输计划 (树上差分+二分答案+LCA)
题目大意:
给 n 个点的树和m个运输计划(s→t),完成所有运输计划的时间是运输计划中的最大时间。现在可以把一条边的边权改为0,求完成所有运输计划的最快时间。
解题分析:
看到最小化最大值的问题,就要想到二分答案。本题主要难点就是二分的check,需要用到树上差分来check,不过本题树上差分的思想并不是很难,主要就是为了统计,在一颗子树内,有多少个距离大于二分枚举最大距离的节点之一在其中(这样才能通过删除根节点到该子树的根节点之间的那条边来缩短查询的节点之间的距离)。主要还是这个删除根到枚举的子树的根节点之间这条边的这个思想比较难吧。因为只允许删一条边,所以这样子处理是挺妙的。
#include <bits/stdc++.h> using namespace std; template<typename T>inline void rd(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define bug cout<<"NO bug!\n"; #define debug(x) cout<<#x<<" "<<x<<"\n"; #define clr(a,b) memset(a,b,sizeof(a)) #define RP(i,s,t) for(int i=s;i<=t;i++) #define rp(i,s,t) for(int i=s;i<t;i++) const int N = 3e5+5 , MAV = 20; int f[N][MAV],n,m,cnt,head[N],dep[N],val[N],dis[N],lca[N],sum[N],dist[N]; struct Edge{ int from,to,w,nxt; }e[N<<1]; struct Ask{ int u,v; }Q[N]; inline void add(int u,int v,int w){ e[++cnt]=(Edge){ u,v,w,head[u] };head[u]=cnt; } void DFS(int u,int pre){ //处理得到每个节点的父亲 和 每个节点的深度,以及 每个节点到根节点的距离 for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v!=pre) //f[v][0]表示v的父亲节点 //最主要的就是这个val[v],记录的就是,由根的方向向下到达v的边权,即每个节点向着更靠近根节点的边的边权,在check()的时候有大用 f[v][0]=u,dep[v]=dep[u]+1,dis[v]=dis[u]+(val[v]=e[i].w),DFS(v,u); } } void initST(){ //初始化st表 rp(j,1,MAV) RP(i,1,n){ f[i][j]=f[f[i][j-1]][j-1]; } } int LCA(int x,int y){ //得到任意两点的LCA if(dep[x]<dep[y])swap(x,y); for(int j=MAV-1;j>=0;j--) if(dep[x]-(1<<j)>=dep[y])x=f[x][j]; if(x==y)return x; for(int j=MAV-1;j>=0;j--) if(f[x][j]!=f[y][j])x=f[x][j],y=f[y][j]; return f[x][0]; } void Count(int u,int pre){ //统计每颗子树的sum值 for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==pre)continue; Count(v,u); sum[u]+=sum[v]; } } bool check(int x){ //用树上差分来进行验证 clr(sum,0); int mx=0,num=0; RP(i,1,m){ int u=Q[i].u,v=Q[i].v; //注意lca处要-2,因为如果两个节点在同一子树,那么删除根到该子树的根节点的边,并不会减少这两个节点之间的距离 if(dist[i]>x)sum[u]++,sum[v]++,sum[lca[i]]-=2,num++,mx=max(mx,dist[i]-x); //mx记录所有运输中多于x的最长时间 } Count(1,-1); //统计所有子树的sum值 RP(i,2,n) if(sum[i]==num && val[i]>=mx)return true;//如果存在一个子树包含所有这些比当前枚举的最长距离要长的节点,并且根节点方向到该子树的根节点这条边的边权大于mx,那么就能通过将这条边的边权置为0,说明当前枚举出的最长距离是可行的 return false; } int main(){ rd(n);rd(m); rp(i,1,n){ int u,v,w;rd(u);rd(v);rd(w); add(u,v,w);add(v,u,w); } RP(i,1,m){ int u,v;rd(u);rd(v); Q[i]=(Ask){ u,v }; } DFS(1,-1);initST(); int l=0,r=0; RP(i,1,m){ int u=Q[i].u,v=Q[i].v; lca[i]=LCA(u,v);r=max(r,(dist[i]=dis[u]+dis[v]-2*dis[lca[i]])); //dist表示对应两点之间的距离 } int ans=0; while(l<=r){ //二分答案,枚举最小的最远距离 int mid=l+r>>1; if(check(mid))r=mid-1,ans=mid; else l=mid+1; } printf("%d\n",ans); }
BZOJ - 3631 松鼠的新家 (LCA+树上差分)
题目大意:
给定一个$n$个节点的无向树,现在某个人按照给定的长度为$n$的序列行走,要求他每到达一个点,那个点应该放置的糖果树就要加一,因为序列最后一个节点是厨房,所以最后一个节点不需要+1,输出每个节点应该放置的糖果数
解题分析:
题意看了半天没看懂,其实就是每次从序列的$a_{i-1}$走到$a_i$,其中的每个节点都$+1$,然后询问每个节点的值。树链剖分裸题,当然,也是树上差分和LCA的裸题,所以本题就用树上差分写,比较简便。需要注意的是:$a_2,a_3 \dots a_{n-1}$都要$-1$,因为重复计算了一次,并且$a_n$也要-1,因为当其作为序列终点时不需要放置糖果。
#include <bits/stdc++.h> using namespace std; #define pb push_back template<typename T>inline void rd(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define RP(i,s,t) for(int i=s;i<=t;i++) const int N = 3e5+5; typedef long long ll; int n,a[N]; vector<int>G[N]; int f[N][30],dep[N]; ll sum[N]; void dfs(int u,int fa){ for(int i=0;i<G[u].size();i++){ int v=G[u][i]; if(v==fa)continue; if(!dep[v]){ dep[v]=dep[u]+1; f[v][0]=u; dfs(v,u); } } } void ST(){ for(int j=1;(1<<j)<=n;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; } int LCA(int a,int b){ if(dep[b]<dep[a])swap(a,b); int d=dep[b]-dep[a]; for(int i=0;(d>>i)!=0;i++) if((d>>i)&1)b=f[b][i]; if(a==b)return b; for(int i=20;i>=0;i--) if(f[b][i]!=f[a][i]) b=f[b][i],a=f[a][i]; return f[b][0]; } void sumdfs(int u,int pre){ for(int i=0;i<G[u].size();i++){ int v=G[u][i]; if(v==pre)continue; sumdfs(v,u); sum[u]+=sum[v]; } } int main(){ rd(n); RP(i,1,n)rd(a[i]); RP(i,2,n){ int u,v;rd(u);rd(v); G[u].pb(v);G[v].pb(u); } dep[1]=1; dfs(1,-1); ST(); RP(i,2,n){ int u=a[i-1],v=a[i]; int lca=LCA(u,v); sum[u]++;sum[v]++;sum[lca]--;sum[f[lca][0]]--; //树上差分部分 } sumdfs(1,-1); RP(i,2,n)sum[a[i]]--; RP(i,1,n)printf("%lld\n",sum[i]); }