洛谷P2680 运输计划(倍增LCA + 树上差分 + 二分答案)
【思路】:
根据题意可以明显看出,当所有任务都完成时的时间是最终的结果,也就是说本题要求,求出最小的最大值。
那这样的话就暗示了将答案二分,进行check。
【check方法】:
如果说当前答案为ans,每个任务设为p[i],所花费的时间是p[i].tim,所有任务p[i].tim的最大值为maxdis
那么则将符合条件p[i].tim>=ans的数量num求出来,这个数量也就是符合条件的路径的数量(一个任务在u,v之间有一个简单路径很容易理解),
然后找到一个所有路径中他们的公共边(公共边就是这个边在符合条件的p[i]中出现的次数==num的边)中最大的一个mmax,如果说
maxdis-mmax<=ans那么check返回1,使得上界变成mid-1,理由是如果最长的路减去一个此时最大的公共边比此时答案ans小,说明ans还可以
继续减小,即让最大值变得更小,反之则不能。
【寻找公共边方法】:
使用树上差分,对任务进行离线处理,定义cnt[x]是x到x的父亲gra[x][0]这条边经过的次数,在check函数中,符合p[i].tim>ans的就让cnt[p[i].u] ++, cnt[p[i].v]++, cnt[p[i].lca] -= 2
所有的任务都判断完成时,进行Dfs更新所有节点的cnt[],当cnt[x]==num && dis[x]-dis[gra[x][0]]>=mmax时更新mmax。进行完Dfs后判断maxdis-mmax与ans关系即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 3e5 + 5; const int maxm = maxn; const int inf = 0x3f3f3f3f; int n, m, mmax, num, ans, maxdis; struct edge{ int to, w, next; } ed[maxn<<1]; struct plan{ int u, v, lca, tim; plan( int u=0, int v=0, int lca=0, int tim=0 ): u(u),v(v),lca(lca),tim(tim){} } p[maxm]; int head[maxn], tot, cnt[maxn]; int gra[maxn][25], dep[maxn], dis[maxn], maxdep; inline int read(){ int k=0, f=1;
char ch=getchar(); while( ch>'9'|| ch<'0' ){ if( ch=='-' ) f = -1; ch = getchar(); } while( ch<='9' && ch>='0' ){ k = k*10+ch-'0'; ch = getchar(); } return k*f; } inline void init(){ memset( head ,-1 ,sizeof(head) ); memset( gra, 0, sizeof(gra) ); maxdep = log(n)/log(2); tot = 1; } inline void add( int u, int v, int w ){ ed[++tot].to = v; ed[tot].w = w; ed[tot].next = head[u]; head[u] = tot; } inline void dfs_lca( int x ){ //初始化与LCA相关的数据 for( int i=1; i<=maxdep; i++ ){ gra[x][i] = gra[gra[x][i-1]][i-1]; if( !gra[x][i] ) break; } for( int i=head[x]; ~i; i=ed[i].next ){ int y = ed[i].to; if( y==gra[x][0] ) continue; dep[y] = dep[x]+1; dis[y] = dis[x]+ed[i].w; gra[y][0] = x; dfs_lca(y); } } inline void dfs_diff( int x ){ for( int i=head[x]; ~i; i=ed[i].next ){ int y = ed[i].to; if(y==gra[x][0]) continue; dfs_diff(y); cnt[x] += cnt[y]; } if( cnt[x]==num && mmax<dis[x]-dis[gra[x][0]] ) //寻找最大的公共边 mmax = dis[x]-dis[gra[x][0]]; } inline void swap( int &a, int &b ){ int t = a; a = b; b = t; } inline int LCA( int x, int y ){ if(dep[x]>dep[y]) swap(x, y); for( int i=maxdep; ~i; i-- ) if( gra[y][i]==x ) return x; //避免卡常,能return就return else if( dep[gra[y][i]]>=dep[x] ) y = gra[y][i]; if( x==y ) return x; //避免卡常 for( int i=maxdep; ~i; i-- ) if( gra[x][i]!=gra[y][i] ){ x = gra[x][i]; y = gra[y][i]; } if( x!=y ) x = gra[x][0]; return x; } inline int max( int a, int b ){ return a>b ? a:b; } inline bool check( int x ){ mmax = num = 0; //每次check都将mmax, num, cnt初始化为0 memset( cnt ,0, sizeof(cnt) ); for( int i=1; i<=m; i++ ){ if( p[i].tim<=x ) continue; num ++; cnt[p[i].u] ++; cnt[p[i].v] ++; cnt[p[i].lca] -= 2; } dfs_diff(1); //更新每个结点的cnt return maxdis-mmax<=x; } int main(){ n = read(); m = read(); //读取方式使用快读,此题卡常数卡的很厉害 init(); int maxe = -inf; for( int i=1; i<n; i++ ){ int u, v, w; u = read(); v = read(); w = read(); add(u, v, w); add(v, u, w); maxe = max( maxe, w ); } dep[1] = 1; dis[1] = 0; dfs_lca(1); maxdis = -inf; for( int i=1; i<=m ;i++ ){ int u, v; u = read(); v = read(); int lca = LCA(u, v); p[i] = plan( u, v, lca, dis[u]+dis[v]-(dis[lca]<<1) ); //储存,后续进行离线处理 maxdis = max( maxdis, p[i].tim ); //获得一条边都不是虫洞的最大值 } int l = maxdis-maxe, r = maxdis; //这里要优化下界l,不然会超时 while( l<=r ){ int mid = (l+r)>>1; if( check(mid) ){ ans = mid; r = mid-1; } else l = mid+1; } printf("%d\n", ans); return 0; }