[noip2015]运输计划(LCA,二分)
运输计划[做题笔记]
挺难绷的。。。
题意
概括:给定 \(n\) 个节点的树和 \(n-1\) 条边的权值,现在可以将一条边的权值改为 \(0\) 。找出一条边,使得将这条边权值赋为 \(0\) 时,\(T\) 组节点 \(u,v\) 之间的距离最大值最小,输出最小值。
思路分析
一开始想假了,天真的以为被 \(T\) 组节点 \(u,v\) 覆盖的次数最多,且权值较大的边就是要删去的边,\(38\)分,查题解才知道是二分答案。
不过确实,求最大值最小
,的确是要二分的。若 \(t1<t2\) ,且 \(t1\) 合法,那么\(t2\)当然合法,单调性不就出来了?明确要二分后,先预处理出 \(T\) 组节点 \(u,v\) 的 \(LCA\) 和距离 \(len\) ,那么答案二分的区间就是 \([0,MAXLEN]\) 。
二分答案的精髓是什么?是check()函数
————miaomiao
首先为了不产生歧义,我们定义节点\(u,v\)之间的树上路径为链,路径长为链长;下文的边即为树的边。
那么,对于当前 \(check\) 的 \(x\),有:
- \(First\),要使 \(x\) 合法,那么删去这条边后,所有链长均小于等于 \(x\)
- \(Second\),要找出这条边,首先要枚举所有的链长 \(len\) ,记录比\(x\)大的个数 \(sum\) ,并将这些链在树上差分。如果有一条边被上述链同时覆盖,显然这就是我们要赋\(0\)的最优边(当然可能存在多条,显然要取边权最大的 \(max\_ len\))
- \(Third\),用 \(MAXLEN-max\_ len\),这是赋\(0\)后的最长链的长,如果结果\(<=x\),显然\(x\)合法(最长边都满足,底下那些小弟就更不用说了)
嗯,一切都这么美妙,但如果不会树上差分,不就祭了?那么,首先我们要会正常的差分 (不会建议回普及组重修),说白了差分就是一个反映相对大小的东西
如果你觉得一个知识点很简单,下一步,把它挂到树上去。
————波波
定义一个差分数组 \(spx[\ ]\) 。对于这种求边被覆盖的次数,首先边权肯定都是\(0\),边上差分很不方便,那么我们把边权下放到点上,\(spx_i\) 表示 \(i\) 和 \(i\) 的父节点之间的边的差分,当我们要将 \(u_i,v_i\) 这条链放到覆盖到树上,显然我们只需要修改 \(u_i\) 节点, \(v_i\) 节点和 \(LCA(u_i,v_i)\) 的差分,\(spx[u_i]\)++, \(spx[v_i]\)++, \(spx[lca(u_i,v_i)]\)-=\(2\)。为啥是\(2\)?很显然我们将链 \((u_i,v_i)\) 分成了 \((u_i,lca_i)\) 和 \((v_i,lca_i)\) 两条链进行差分,显然要减\(2\)。
差分完之后,实际的边权\(k\)就是它的子树中差分数组的和(类似于正常差分中前缀和),这可以通过一遍\(dfs\)实现。
服了,求\(LCA\)必须用\(tarjan\),倍增过不了,卡\(95\)……挺难崩的,现学\(tarjan\)求\(LCA\)(狂\(D\)不止)不过\(tarjan\)求确实比倍增快,\(O(n+m)\) 比 \(O(n\ log_2n)\) 友好多了
呃,如果有dalao发现我的代码和您差不多,那我可能是贺的您的,别D我(逃,%%%
\(AC \ code\)
#include<bits/stdc++.h> using namespace std; #define endl '\n' #define N 300010 #define int long long int n,m;int ans; struct EDGE{ int next,to,t; }e[N<<1],d[N<<1];//q是存原树,d是存查询的T组u,v构成的树,为了tarjan求LCA而生 int spx[N];//这是差分数组,数组名只是数组名,没啥特殊含义 int he[N],tot; int hd[N]; int edge[N];//树上差分,将边权下放到点上进行差分,edge[i]表示节点i代表的边 int MAXLEN;//最长路径 struct SOLVE{ int u,v,lca,len; }q[N];//这个结构体存的是T条链(ui,vi)的信息 inline int read() { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } void add(int u,int v,int t) { tot++; e[tot].to=v; e[tot].next=he[u]; he[u]=tot; e[tot].t=t; return ; } void add_d(int u,int v) { tot++; d[tot].to=v; d[tot].next=hd[u]; hd[u]=tot; return; } int fa[N];//并查集 int dis[N];//前缀和方便计算链长 int find(int x) { if(x==fa[x]) return x; return fa[x]=find(fa[x]); } bool f[N]; void tarjan(int x,int pre)//tarjan求LCA { int y; for(int i=he[x];i>0;i=e[i].next) { y=e[i].to; if(y!=pre) { edge[y]=i; dis[y]=dis[x]+e[i].t; tarjan(y,x); int fa_x=find(x); int fa_y=find(y); if(fa_x!=fa_y) fa[fa_y]=fa_x; f[y]=1; } } for(int i=hd[x];i>0;i=d[i].next) { y=d[i].to; if(f[y]){ int now=(i+1)>>1;//因为存的是双向边,所以第i条边对应的编号为(i+1)/2 q[now].lca=find(y); q[now].len=dis[x]+dis[y]-2*dis[q[now].lca]; MAXLEN=max(MAXLEN,q[now].len); } } } int sum,max_len;//sum代表比当前check的x大的边的个数,即需要减小的边数 void dfs_spx(int x,int pre)//树上差分 { int y; for(int i=he[x];i>0;i=e[i].next) { y=e[i].to; if(y!=pre) { dfs_spx(y,x); spx[x]+=spx[y]; } } if(spx[x]==sum)//如果该边能够让所有链都覆盖 max_len=max(max_len,e[edge[x]].t); } bool check(int x) { for(int i=0;i<=n;i++) spx[i]=0; sum=0;max_len=0; for(int i=1;i<=m;i++) { if(q[i].len>x) { sum++; spx[q[i].u]++; spx[q[i].v]++; spx[q[i].lca]-=2; } } dfs_spx(1,0); if(MAXLEN-max_len<=x) return 1; return 0; } signed main() { n=read();m=read(); for(int i=1;i<n;++i) { int a,b,c; a=read(),b=read(),c=read(); add(a,b,c);add(b,a,c); } tot=0; for(int i=1;i<=n;++i) fa[i]=i;//并查集初始化 for(int i=1;i<=m;++i) { int u,v; u=read(),v=read(); q[i].u=u;q[i].v=v; add_d(u,v);add_d(v,u); } tarjan(1,0); int st=0,ed=MAXLEN; while(st<ed) { int mid=(st+ed)>>1; if(check(mid)) ed=ans=mid; else st=mid+1; } write(ans); return 0; }
\(Update 5.15\)
貌似树剖求 \(LCA\) 更快,因为它常数小且严格跑不满,所以 \(O(nlogn)\) 此时比 \(O(n+m)\) 还快。。。
点击查看代码
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 300010 int n,m; int MAX; struct EDGE{int next,to,t;}e[N<<1]; int head[N],total; void add(int u,int v,int t){e[++total]={head[u],v,t};head[u]=total;} int edge[N]; namespace Tree_Chain_Partition{ int fa[N],siz[N],wson[N],depth[N]; int top[N]; int dis[N]; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; edge[y]=i; dis[y]=dis[x]+e[i].t; fa[y]=x; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; if(wson[x]) dfs2(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==wson[x]||y==fa[x]) continue; dfs2(y,y); } } int LCA(int u,int v){ while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); u=fa[top[u]]; } return (depth[u]<depth[v]?u:v); } } using namespace Tree_Chain_Partition; int u[N],v[N],lca[N],len[N]; int b[N]; int sum,reduce; void dfs(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; dfs(y); b[x]+=b[y]; } if(b[x]==sum) reduce=max(reduce,e[edge[x]].t); } bool check(int x){ for(int i=1;i<=n;i++) b[i]=0; sum=reduce=0; for(int i=1;i<=m;i++){ if(len[i]>x){ sum++; b[u[i]]++; b[v[i]]++; b[lca[i]]-=2; } } dfs(1); if(MAX-reduce<=x) return 1; return 0; } int ans; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int a,b,c,i=1;i<n;i++){ a=read,b=read,c=read; add(a,b,c),add(b,a,c); } dfs1(1),dfs2(1,1); for(int i=1;i<=m;i++){ u[i]=read,v[i]=read; lca[i]=LCA(u[i],v[i]); len[i]=dis[u[i]]+dis[v[i]]-2*dis[lca[i]]; MAX=max(MAX,len[i]); } int st=0,ed=MAX; while(st<ed){ int mid=(st+ed)>>1; if(check(mid)) ed=ans=mid; else st=mid+1; } write(ans); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步