[luogu]P2680 运输计划

原题链接 :P2680 运输计划

分析

题意很简单,给定一张连通图,n个点,n-1条边,很显然是一棵树。
现在给定m条链\((u,v)\)。
现在可以cut掉一条边(边权置为0)。
现在求最长链的最小值。

55pts

蒟蒻的我肯定是拿不到满分的。。我们直接考虑部分分。
对于m=1的点,我们只需要枚举链上的全部边,然后cut掉最长的那一个就可以了。
裸搜一遍dfs,然后求出链长,最后减去最长边就可以了。
然后是第i条航线链接i和i+1点的点。
很明显整张图形成了一条链。
前缀和维护整张图,这样子可以把查询优化到\(O(1)\)。
然后枚举最长链上的每一条边,考虑把它cut掉之后的答案,然后求一下最小值即可。
这样子55pts就轻松到手了,时间不到20分钟。

100pts

但现在不是考场啊!!!
所以我们要考虑正解啊。
首先考虑怎么在图上求出每一条链的长度。
我们可以利用LCA求出每两个点的公共祖先,在求的同时枚举每一个点到1号点的距离(要是怕卡的话可以随机化初始节点)。
然后链\((u,v)\)的长度就是\(dis_u+dis_v-2\times dis_{lca_{(u,v)}}\)
这样子我们就可以O(1)查询每条链的长度了。
然后直接暴力枚举最长链上的边,然后删边就可以了。
...
...
哪有这么简单。
求LCA的时间复杂度为\(O(nlogn)\)而暴力枚举的最坏复杂度为\(O(n^2)\)这样子肯定是通不过全部的数据的。
我们考虑要降低这个暴力枚举的复杂度。
由于我们只需要求最长链,我们可以考虑二分优化。
二分出最长链的长度mid,然后我们只需要对大于这个长度的链进行考虑就可以了。
我们对每一条边求出它和所需要的长度的差值,并且统计出最大的差值。
然后我们就要对大于这个差值的边进行考虑。
由于只有在链上的边被删除了才有用,我们就需要找出是否有(大于这个最大差值并且在所有长度大于mid的链上)的边。
暴力枚举每一条边?
时间复杂度又变回\(O(n^2)\)了,行不通。
那么我们就考虑一个\(O(n)\)的算法。
发现之前LCA的dfs里可以统计出每个节点的dfn(dfs)序,而这个序是非常有用的。
我们可以发现每一个节点的子节点都在它的后面,也就是说只要从后往前推,我们就可以不重不漏地一遍更新所有的节点。
考虑利用树上差分思路。
我们给每一条链的起点和终点打上添加标记(+1),在他们的LCA上打上删除标记(-2),这样我们在上推的时候就可以在起点和终点处施加影响,然后在他们的LCA处消除影响,我们就实现了\(O(n)\)统计每一条边在链集里的出现次数。
然后\(O(m)\)跑过每一条边,如果发现边的出现次数等于边集的大小并且边的大小大于之前所求出的最大差值,我们直接返回1,如果遍历完所有的边都没有的话,我们返回0。
这样子就可以在\(O(nlogn)\)的时间复杂度内解决问题了。
下面给出55pts和100pts的代码。。

代码

55pts

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
using namespace std;
const int Maxn=300009;
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int u,v,w;
int n,m,d[Maxn],len=0,le=0,flag;
int ed[Maxn][3],fa[Maxn],f[Maxn];
int head[Maxn],edge[Maxn],nxt[Maxn],ver[Maxn],tot=1;
void dfs(int x,int k){
    if(x==v){
        printf("%d\n",k-le);
        exit(0);
    }
    //printf("%d\n",x);
    f[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int tmp=le;
        int y=ver[i];
        if(!f[y]){
            le=max(edge[i],le);
            dfs(y,k+edge[i]);
        }
        le=tmp;
    }
}
void work1();
void work2();
int main()
{
    n=read();m=read();
    if(m==1)work1();
    else work2();
    return 0;
}
void work1(){
    for(int i=1;i<n;i++){
        u=read();v=read();w=read();
        ver[++tot]=v;nxt[tot]=head[u];edge[tot]=w;head[u]=tot;
        ver[++tot]=u;nxt[tot]=head[v];edge[tot]=w;head[v]=tot;
    }
    u=read();v=read();
    dfs(u,0);
}
void work2(){
    int k=0,ans=0x3f3f3f3f;
    for(int i=2;i<=n;i++){
        read();read();d[i]=read();
        d[i]+=d[i-1];
    }
    for(int i=1;i<=m;i++){
        u=read();v=read();
        ed[i][1]=u;
        ed[i][2]=v;
        if(!k||d[v]-d[u]>d[ed[k][2]]-d[ed[k][1]])k=i;
    }
    for(int i=ed[k][1]+1;i<=ed[k][2];i++){
        int cut=d[i]-d[i-1],f;
        for(int i=1;i<=m;i++){
            f=(ed[k][1]<i&&ed[k][2]>=i);
            ans=min(ans,d[ed[k][2]]-d[ed[k][1]]-cut*f);
        }
    }
    printf("%d\n",ans);

}
/* There is n nodes and the graph is connected ,we can easily know it is a tree
 * And there is m chains that connect u and v
 * Our task is choose an edge and make its right be 0 ,making the ;ongest chain shorest
 * Get part marks:
 * Edge i connect i and i+1.
 * The graph become a chain.
 * We can easily get the marks.
 * Consider we must cut the edge on the longest chain.
 */

100pts

#include <bits/stdc++.h>
using namespace std;
const int Maxn=300009*2;
struct QAQ{
    int u,v,lcaa,diss;
}t[Maxn];
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int n,m,u,v,w,cnt,num[Maxn],deep[Maxn],vis[Maxn],fa[Maxn][30],tmp[Maxn];
int head[Maxn],edge[Maxn],nxt[Maxn],ver[Maxn],tot=1,dis[Maxn];
void dfs(int x,int dep){
    num[++cnt]=x;
    deep[x]=dep;
    vis[x]=1;
    for(int i=1;i<25;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=nxt[i])if(!vis[ver[i]]){
        fa[ver[i]][0]=x;
        dis[ver[i]]=dis[x]+edge[i];
        dfs(ver[i],dep+1);
    }
}
int lca(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    int t=deep[x]-deep[y];
    for(int i=0;i<25;i++)
        if((1<<i)&t)x=fa[x][i];
    if(x==y)return x;
    for(int i=24;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
bool check(int maxn){
    int cnt=0,ans=0;
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=m;i++)if(t[i].diss>maxn){
        tmp[t[i].u]++;tmp[t[i].v]++;tmp[t[i].lcaa]-=2;
        ans=max(ans,t[i].diss-maxn);
        cnt++;
    }
    if(!cnt)return true;
    for(int i=n;i>=1;i--)tmp[fa[num[i]][0]]+=tmp[num[i]];
    for(int i=2;i<=n;i++)if(tmp[i]==cnt&&dis[i]-dis[fa[i][0]]>=ans)return true;
    return false;
}
int main()
{
    //freopen("data.in","r",stdin);
    int summ=0;
    n=read();m=read();
    for(int i=1;i<n;i++){
        u=read();v=read();w=read();
        ver[++tot]=v;nxt[tot]=head[u];edge[tot]=w;head[u]=tot;
        ver[++tot]=u;nxt[tot]=head[v];edge[tot]=w;head[v]=tot;
        summ+=w;
    }
    dis[1]=0;
    dfs(1,1);
    //cout<<lca(4,5)<<endl;
    //dfs for one ,remark every node ,and Mul array.
    for(int i=1;i<=m;i++){
        u=read();v=read();
        t[i].u=u;t[i].v=v;
        t[i].lcaa=lca(u,v);
        t[i].diss=dis[u]+dis[v]-2*dis[t[i].lcaa];
        //cout<<t[i].diss<<endl;
    }
    //get each node's father
    int l=0,r=summ,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",l);
    return 0;
}

原文地址:https://www.cnblogs.com/onglublog/p/9865112.html

时间: 2024-08-28 14:24:23

[luogu]P2680 运输计划的相关文章

洛谷——P2680 运输计划

https://www.luogu.org/problem/show?pid=2680 题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 t

[NOIP2015] 提高组 洛谷P2680 运输计划

题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创新,L 国国王同意小 P 的

P2680 运输计划

题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创新,L 国国王同意小 P 的

P2680 运输计划[二分+LCA+树上差分]

题目描述 公元20442044 年,人类进入了宇宙纪元. L 国有 nn 个星球,还有 n-1n?1 条双向航道,每条航道建立在两个星球之间,这 n-1n?1 条航道连通了 LL 国的所有星球. 小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u_i*u**i* 号星球沿最快的宇航路径飞行到 v_i*v**i* 号星球去.显然,飞船驶过一条航道是需要时间的,对于航道 jj,任意飞船驶过它所花费的时间为 t_j*t**j*,并且任意两艘飞船之间不会产生任

P2680 运输计划 树链剖分

在一颗有边权的树上有m条路径,清零一条边的边权使得m条路径的最大值最小. 输出这个最大值 显然 要遍历这m条路的最长路(如果最长路有多条随意遍历一条即可) 因为树上距离不修改  那么用前缀和维护树上路径长度可以少一个log 然后遍历最长路的每一条边  ans=min(ans,max(最长路的长度-这条路的长度,不经过这条边的最长路长度) 所以现在需要维护的是不经过这条边的最长路长度 可以遍历m  将不在这条路径上的所有边 维护一个值(就是这条路径的长度) 用线段树维护最大值即可 #include

【NOIP2015提高组】运输计划

https://daniu.luogu.org/problem/show?pid=2680 使完成所有运输计划的时间最短,也就是使时间最长的运输计划耗时最短.最大值最小问题考虑用二分答案,每次check(mid)检查时间最长的运输计划耗时是否小于等于mid,二分出使得check(mid)==true的最小mid值. check函数怎么写是本题的难点.耗时小于mid的运输计划不会影响check的结果.耗时大于mid的运输计划肯定需要改造他们的共同边才有可能使它们耗时都小于mid,而有多条共同边的时

[noip 2015]运输计划 [LCA][树链剖分]

用了luogu上的题目描述 题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创

NOIp2015 运输计划 [LCA] [树上差分] [二分答案]

我太懒了 吃掉了题面 题解 & 吐槽 一道很好的树上差分练习题. 不加fread勉强a过bzoj和luogu的数据,加了fread才能在uoj里卡过去. 可以发现,答案则是运输计划里花费的最大值,最大值最小,便是二分答案的标志. 那么该怎么check呢... 我们得找出所有超过限制的计划,这个过程可以在LCA倍增的过程中预处理出来. 然后再找出一些被这些计划都覆盖的边,找到最大的那条边,如果最大的计划花费减去最大的那条边小于x,那么x就是可行的. 但是该怎么找到那些被计划都覆盖的边呢... 我们

树链剖分-Hello!链剖-[NOIP2015]运输计划-[填坑]

This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://www.cnblogs.com/JasonCow/ [NOIP2015]运输计划    Hello!链剖.你好吗? 题意: 给出一棵n个节点的带权树,m对树上点对 现在允许删除一条边,(权值修改为0) 输出: 最小化的点对间最大距离 1.链剖 2.树上差分 3.二分 链剖我就不多说了,就是两dfs 注意