Luogu P2680 [NOIp提高组2015]运输计划

本文介绍了一种物流路径优化算法,该算法通过二分查找、树上差分和最近公共祖先查询(LCA)等技术,解决在一个有n个节点和n-1条边的树状图中寻找最佳路径的问题,以最小化物流飞船完成所有任务所需的时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


题目描述 传送门
L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物
流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰。
为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后, 这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的 物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞,试求出小 P 的物流公司完成阶段时要的最短时间是多少?


树链剖分+线段树暴力尝试修改所有路径40分,,,
正解:二分答案+树上差分+LCA(我用的树链剖分)
大致思路是:先把无根树转化成有根树,预处理lca和运输的路径长,然后二分答案,对于大于二分答案的路径,需要尝试删除其中一条边来满足答案,易知需要删除的边一定是这些路径的交集,利用树上差分求出每条边被多少条大于答案的路径覆盖,方法是把一条 (u,v) 路径分成两条 (u,lca(u,v)),(v,lca(u,v)) 的链,类似差分数列,给最前面的+1,超尾位置-1,前缀和就是实际值,那么就是sum[u]–,sum[v]–,sum[lca(u,v)]-=2;最后根据dfs序递推出前缀和,贪心尝试删除最大的那条交边,继续二分直到不能分。
代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3e5+10;
int n,m;
struct Edge{
    int to,nxt,d;
    Edge(int a=0,int b=0,int c=0):to(a),nxt(b),d(c){}
}v[maxn*2];
int len=0,cnt=0,l=-1,r=0;
int h[maxn],fa[maxn],sz[maxn],son[maxn],lca[maxn],seq[maxn],dist[maxn],cost[maxn],depth[maxn],top[maxn],s[maxn],t[maxn],sum[maxn];
void dfs1(int u,int f){
    fa[u]=f;
    sz[u]=1;
    son[u]=0;
    depth[u]=depth[f]+1;
    for(int i=h[u];i;i=v[i].nxt) if(v[i].to!=f){
        dist[v[i].to]=dist[u]+v[i].d;
        dfs1(v[i].to,u);
        sz[u]+=sz[v[i].to];
        if(sz[v[i].to]>sz[son[u]]) son[u]=v[i].to;
    }
    seq[len++]=u;
}
void dfs2(int u,int tp){
    top[u]=tp;
    if(sz[u]>1) dfs2(son[u],tp);
    for(int i=h[u];i;i=v[i].nxt) if(v[i].to!=fa[u]&&v[i].to!=son[u])
        dfs2(v[i].to,v[i].to);
}
int getlca(int x,int y){
    int f1=top[x],f2=top[y],ans=0;
    while(f1!=f2){
        if(depth[f1]<depth[f2]) swap(f1,f2),swap(x,y);
        x=fa[f1];
        f1=top[x];        
    }
    if(x==y) return x;
    if(depth[x]<depth[y]) swap(x,y);
    return y;
}
bool check(int a){
    memset(sum,0,sizeof(sum));
    int tot=0,ans=0;
    for(int i=0;i<m;i++) if(cost[i]>a){
        sum[lca[i]]-=2;
        sum[s[i]]++;
        sum[t[i]]++;
        tot++;
    }
    for(int i=0;i<len;i++) sum[fa[seq[i]]]+=sum[seq[i]];
    for(int i=2;i<=n;i++) if(sum[i]==tot) ans=max(ans,dist[i]-dist[fa[i]]);
    for(int i=0;i<m;i++) if(cost[i]-ans>a) return false;
    return true;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(h,0,sizeof(h));
    for(int i=1;i<n;i++){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        v[++cnt]=Edge(b,h[a],w);
        h[a]=cnt;
        v[++cnt]=Edge(a,h[b],w);
        h[b]=cnt;
        r+=w;
    }
    depth[1]=sz[0]=dist[1]=0;
    dfs1(1,1);
    dfs2(1,1);
    for(int i=0;i<m;i++){
        scanf("%d%d",&s[i],&t[i]);
        lca[i]=getlca(s[i],t[i]);
        cost[i]=dist[s[i]]+dist[t[i]]-2*dist[lca[i]];
    }
    while(l<r-1){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid;
    }
    printf("%d\n",r);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值