luogu2680 运输计划 题解
最小化完成所有任务的时间,考虑二分答案。
用 \(lca\) 算法预处理每个计划的距离,设第 \(i\) 个计划的距离为 \(W_i\)
如何判断完成任务的时间 \(t\) 是否可行呢?
不难想到,有以下两种情况。
- $ _{1 i m}{ { W_i }} t$。
- 将一条边权 为 \(dlt\) 的边改为 0(虫洞)后,$ _{1 i m}{ { W_i - dlt }} t$。
第一种情况很容易判断。
对于第二种情况,不难发现:将一条边权为 \(len\) 的边改为 0 后能使所有距离大于 \(t\) 的计划距离都变成不大于\(t\),当且仅当这些计划交于此边,且 $ len _{1 i m}{ { W_i - t }}$
这是显然的,证明略。
明确这个之后,问题仅仅在于,如何快速统计每条边被计划经过的次数和权。
边权不难求,用倍增 \(lca\) 的 \(f\) 与 \(dis\) 数组即可。设 \((x \rightarrow f(x,0))\) 边权为 \(d_x\),到根的距离为 \(dis_x\)
则 $ d_x=dis_x-dis(f(x,0)) $
考虑统计经过次数。
如果朴素地去统计,复杂度是 $ O(n)$ 的。可以用树上差分。
用一个计数数组 $ K$ 记录差分,计划 $ (x y)$,令 \[ K(x)+1,K(y+1),K(z)-2 \quad z=lca(x,y) \] 然而这个不像一般树上差分一样,统计子树信息。而且上述做法是边权差分,需要转化成点。
考虑每个计划路径是一条链,所以只需要统计每条链的信息就行了。
我们记录搜索树中每次访问到的点,即为 $ idf$。因为搜索树是一个 DAG,所以这个东西倒序就是逆拓扑序。
按照这个顺序令 $ K(f(idf(x),0))+K(idf(x))$ 就能自底向上统计出每个点的信息。
然后判断上述两种情况就行了。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=3e5+10;
int n, m, cnt, d[N], dis[N], f[N][18], k[N], idf[N];
int c, h[N], ver[N<<1], nxt[N<<1], w[N<<1];
struct E { int x, y, z, w; } g[N<<1];
void add(int x,int y,int z) { ver[++c]=y, w[c]=z, nxt[c]=h[x], h[x]=c; }
int r_() {
int a=0; char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);a=(a<<1)+(a<<3)+(c^48),c=getchar());
return a;
}
void dfs(int x,int fr) {
int i, y, z;
idf[++cnt]=x, d[x]=d[fr]+1, f[x][0]=fr;
for(i=1;(1<<i)<d[x];++i) f[x][i]=f[f[x][i-1]][i-1];
for(i=h[x];i;i=nxt[i]) if(ver[i]!=fr) {
y=ver[i], z=w[i];
dis[y]=dis[x]+z, dfs(y,x);
}
}
int lca(int x,int y) {
int i, j;
if(d[x]<d[y]) swap(x,y);
for(i=0,j=d[x]-d[y];j;++i,j>>=1) if(j&1) x=f[x][i];
if(x==y) return x;
for(i=17;i>=0;--i) if(f[x][i]!=f[y][i]) x=f[x][i], y=f[y][i];
return f[x][0];
}
bool C(int x) {
int i, tot=0, ans=0;
memset(k,0,sizeof(k));
for(i=1;i<=m;++i) if(g[i].w>x) {
++k[g[i].x], ++k[g[i].y], k[g[i].z]-=2;
ans=max(ans,g[i].w-x), ++tot;
}
if(!tot) return 1;
for(i=n;i;--i) k[f[idf[i]][0]]+=k[idf[i]];
for(i=2;i<=n;++i) if(k[i]==tot&&dis[i]-dis[f[i][0]]>=ans) return 1;
return 0;
}
int main() {
int i, x, y, z, l=0, r=0, mid;
n=r_(), m=r_();
for(i=1;i<n;++i) {
x=r_(), y=r_(), z=r_();
r+=z, add(x,y,z), add(y,x,z);
}
dfs(1,0);
for(i=1;i<=m;++i) {
x=r_(), y=r_(), z=lca(x,y);
g[i].x=x, g[i].y=y, g[i].z=z;
g[i].w=dis[x]+dis[y]-(dis[z]<<1);
}
while(l<r) {
mid=l+r>>1;
if(C(mid)) r=mid; else l=mid+1;
}
printf("%d\n",l);
}
luogu2680 运输计划 题解
https://yozora0908.github.io/2021/lg2680-solution/