zoukankan      html  css  js  c++  java
  • 【15NOIP提高组】运输计划(洛谷P2680)(Acwing.521)(一本通1892)

    【题目描述】

    公元2044年,人类进入了宇宙纪元。

    L国有n个星球,还有n-1条双向航道,每条航道建立在两个星球之间,这n-1条航道连通了L国的所有星球。

    小P掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从ui号星球沿最快的宇航路径飞行到vi号星球去。显然,飞船驶过一条航道是需要时间的,对于航道j,任意飞船驶过它所花费的时间为tj,并且任意两艘飞船之间不会产生任何干扰。

    为了鼓励科技创新,L国国王同意小P的物流公司参与L国的航道建设,即允许小P把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

    在虫洞的建设完成前小P的物流公司就预接了m个运输计划。在虫洞建设完成后, 这m个运输计划会同时开始,所有飞船一起出发。当这m个运输计划都完成时,小P的物流公司的阶段性工作就完成了。

    如果小P可以自由选择将哪一条航道改造成虫洞,试求出小P的物流公司完成阶段性工作所需要的最短时间是多少?

    【输入】

    第一行包括两个正整数 n、m,表示L国中星球的数量及小P公司预接的运输计划的数量,星球从1到n编号。

    接下来n-1行描述航道的建设情况,其中第i行包含三个整数ai,bi和ti,表示第i条双向航道修建在ai与bi两个星球之间,任意飞船驶过它所花费的时间为ti。接下来m行描述运输计划的情况,其中第j行包含两个正整数uj和vj,表示第j个运输计划是从 uj号星球飞往vj号星球。

    【输出】

    共1行,包含1个整数,表示小P的物流公司完成阶段性工作所需要的最短时间。

    【输入样例】

    6 3 
    1 2 3 
    1 6 4 
    3 1 7 
    4 3 6 
    3 5 5 
    3 6 
    2 5 
    4 5

    【输出样例】

    11
    

    【提示】

    输入输出样例1说明:

    将第1条航道改造成虫洞:则三个计划耗时分别为:11、12、11,故需要花费的时间为12。

    将第2条航道改造成虫洞:则三个计划耗时分别为:7、15、11,故需要花费的时间为15。

    将第3条航道改造成虫洞:则三个计划耗时分别为:4、8、11,故需要花费的时间为11。

    将第4条航道改造成虫洞:则三个计划耗时分别为:11、15、5,故需要花费的时间为15。

    将第5条航道改造成虫洞:则三个计划耗时分别为:11、10、6,故需要花费的时间为11。

    故将第3条或第5条航道改造成虫洞均可使得完成阶段性工作耗时最短,需要花费的时间为11。

    其它输入输出样例下载

    所有测试数据的范围和特点如下表所示:

    对于100%的数据,100n3000001m300000

    请注意常数因子带来的程序效率上的影响。


     阅读完题目我们发现,本题是一道求最大值最小的问题,那么考虑二分花费的时间。

    在check函数中,我们找出所有总长度大于mid的路径,那么要使mid时间内这些路径都能通过,就要使这些路径的长度都减小(记住是同时出发的哦)

    因此我们考虑找到一条这些路径都通过的边,并且这条边的边权尽可能大,将这条边变成虫洞。如果总长度大于mid的路径中最长的那条路径的长度 - 这条边的边权<=mid,那么在mid时间内,就一定可以通过所有的路径;反之,就不可以。

    那接下来考虑怎么找这条边呢。类似以前做过的这道题:暗的连锁(信息学奥赛一本通 1553),可以用树上差分算法解决这一类问题:一棵树上有若干条指定路径,求每条边有几条路径经过。

    简单来说,就是对每一条路径,它的起点x和终点y的点权都++,而LCA( x, y )的点权- -,LCA( x, y )的父亲结点的点权- -。在修改操作结束之后,用一次DFS求出每一个点的子树和就是每一个点修改后的点权。至于这个算法的正确性,有兴趣可以深入研究一下,我在这里就不做说明了。

    在这道题中,执行完树上差分后,如果一条边的两个端点的点权都等于 总长度大于mid的路径的数量(即这条边被这些路径经过,这些路径都通过这条边),那么这条边就是备选边。再找到备选边里最长的那条边,将其改造成虫洞。

    详情见代码——

      1 #include<bits/stdc++.h>
      2 //#define int long long
      3 #define R register int
      4 #define PII pair<int,int>
      5 using namespace std;
      6 const int N=300005,inf=1e12;
      7 inline int read()
      8 {
      9     char ch=getchar();int num=0;bool flag=false;
     10     while(ch<'0'||ch>'9'){if(ch=='-')flag=true;ch=getchar();}
     11     while(ch>='0'&&ch<='9'){num=num*10+ch-'0';ch=getchar();}
     12     return flag?-num:num;
     13 }
     14 int n,m,cnt,head[N],dis[N],f[N][20],sum[N],deep[N];
     15 struct Node{
     16     int x,y,z;
     17     bool operator <(const Node &b)const
     18     {
     19         return z>b.z;
     20     }
     21 }ed[N<<1];
     22 struct node{
     23     int nxt,to,w;
     24 }e[N<<1];
     25 struct NODE{
     26     int x,y,lca,dis;
     27     bool operator <(const NODE &b)const
     28     {
     29         return dis>b.dis;
     30     }
     31 }way[N];
     32 void add(int x,int y,int z)
     33 {
     34     e[++cnt].nxt=head[x];
     35     e[cnt].to=y;
     36     e[cnt].w=z;
     37     head[x]=cnt;
     38 }
     39 void dfs(int x,int fa)
     40 {
     41     //dfn[++T]=x;
     42     deep[x]=deep[fa]+1;
     43     f[x][0]=fa;
     44     for(R i=head[x];i;i=e[i].nxt)
     45     {
     46         int t=e[i].to;
     47         if(t==fa)continue;
     48         dis[t]=dis[x]+e[i].w;
     49         dfs(t,x);
     50     }
     51 }
     52 int lca(int x,int y)
     53 {
     54     if(deep[x]<deep[y])swap(x,y);
     55     for(R j=17;j>=0;j--)
     56         if(deep[f[x][j]]>=deep[y])
     57             x=f[x][j];
     58     if(x==y)return x;
     59     for(R j=17;j>=0;j--)
     60         if(f[x][j]!=f[y][j])
     61             x=f[x][j],y=f[y][j];
     62     return f[x][0];
     63 }
     64 void getsum(int x,int fa)
     65 {
     66     for(R i=head[x];i;i=e[i].nxt)
     67     {
     68         int t=e[i].to;
     69         if(t==fa)continue;
     70         getsum(t,x);
     71         sum[x]+=sum[t];
     72     }
     73 }
     74 bool check(int x)
     75 {
     76     if(way[1].dis<=x)return true;
     77     for(R i=1;i<=n;i++)sum[i]=0;
     78     int tot=0;
     79     for(R i=1;i<=m;i++)
     80     {
     81         if(way[i].dis<=x)break;
     82         tot++;//tot为总长度大于mid的路径的数量 
     83         sum[way[i].x]++;sum[way[i].y]++;
     84         sum[way[i].lca]--;sum[f[way[i].lca][0]]--;//树上差分 
     85     }
     86     getsum(1,0);//DFS求每一个点的子树和 
     87     
     88     /*另一种做法,待会介绍
     89     for(R i=T;i>1;i--)
     90     sum[f[dfn[i]][0]]+=sum[dfn[i]];*/
     91     
     92     int ma=0;
     93     for(R i=1;i<n;i++)
     94         if(sum[ed[i].x]==tot&&sum[ed[i].y]==tot)//一条边的两个端点的点权都等于tot
     95         {
     96             ma=ed[i].z;
     97             break;
     98         }
     99     if(way[1].dis-ma<=x)return true;
    100     return false;
    101 }
    102 signed main()
    103 {
    104     n=read();m=read();
    105     for(R i=1;i<n;i++)
    106     {
    107         int a=read(),b=read(),c=read();
    108         add(a,b,c);add(b,a,c);
    109         ed[i].x=a;ed[i].y=b;ed[i].z=c; 
    110     }
    111     sort(ed+1,ed+n);//先给所有边排个序,方便check的时候找最长边
    112     dfs(1,0);
    113     for(R j=1;j<=17;j++)
    114         for(R i=1;i<=n;i++)
    115             f[i][j]=f[f[i][j-1]][j-1];//我用的是倍增法求LCA 
    116     for(R i=1;i<=m;i++)
    117     {
    118         int u=read(),v=read();
    119         way[i].x=u;way[i].y=v;
    120         way[i].lca=lca(u,v);//预处理出所有路径的相关信息 
    121         way[i].dis=dis[u]+dis[v]-2*dis[way[i].lca];
    122     }
    123     sort(way+1,way+1+m);//记得排序 
    124     int l=way[1].dis-ed[1].z,r=way[1].dis;//优化一下二分,将l设置成可能的最小值,这样数据的最后一个点就不容易超时 
    125     while(l<r)
    126     {
    127         int mid=l+r>>1;
    128         if(check(mid))r=mid;
    129         else l=mid+1;
    130     }
    131     printf("%d",l);
    132     return 0;
    133 }

     上面的代码是DFS求每一个点的子树和,下面还有一种稍微简单一点的方法(本质上没区别)

      1 #include<bits/stdc++.h>
      2 //#define int long long
      3 #define R register int
      4 #define PII pair<int,int>
      5 using namespace std;
      6 const int N=300005,inf=1e12;
      7 inline int read()
      8 {
      9     char ch=getchar();int num=0;bool flag=false;
     10     while(ch<'0'||ch>'9'){if(ch=='-')flag=true;ch=getchar();}
     11     while(ch>='0'&&ch<='9'){num=num*10+ch-'0';ch=getchar();}
     12     return flag?-num:num;
     13 }
     14 int n,m,cnt,head[N],dis[N],f[N][20],sum[N],deep[N],T,dfn[N];
     15 struct Node{
     16     int x,y,z;
     17     bool operator <(const Node &b)const
     18     {
     19         return z>b.z;
     20     }
     21 }ed[N<<1];
     22 struct node{
     23     int nxt,to,w;
     24 }e[N<<1];
     25 struct NODE{
     26     int x,y,lca,dis;
     27     bool operator <(const NODE &b)const
     28     {
     29         return dis>b.dis;
     30     }
     31 }way[N];
     32 void add(int x,int y,int z)
     33 {
     34     e[++cnt].nxt=head[x];
     35     e[cnt].to=y;
     36     e[cnt].w=z;
     37     head[x]=cnt;
     38 }
     39 void dfs(int x,int fa)
     40 {
     41     dfn[++T]=x;//dfs的时候记录一下每个点的时间戳 
     42     deep[x]=deep[fa]+1;
     43     f[x][0]=fa;
     44     for(R i=head[x];i;i=e[i].nxt)
     45     {
     46         int t=e[i].to;
     47         if(t==fa)continue;
     48         dis[t]=dis[x]+e[i].w;
     49         dfs(t,x);
     50     }
     51 }
     52 int lca(int x,int y)
     53 {
     54     if(deep[x]<deep[y])swap(x,y);
     55     for(R j=17;j>=0;j--)
     56         if(deep[f[x][j]]>=deep[y])
     57             x=f[x][j];
     58     if(x==y)return x;
     59     for(R j=17;j>=0;j--)
     60         if(f[x][j]!=f[y][j])
     61             x=f[x][j],y=f[y][j];
     62     return f[x][0];
     63 }
     64 void getsum(int x,int fa)
     65 {
     66     for(R i=head[x];i;i=e[i].nxt)
     67     {
     68         int t=e[i].to;
     69         if(t==fa)continue;
     70         getsum(t,x);
     71         sum[x]+=sum[t];
     72     }
     73 }
     74 bool check(int x)
     75 {
     76     if(way[1].dis<=x)return true;
     77     for(R i=1;i<=n;i++)sum[i]=0;
     78     int tot=0;
     79     for(R i=1;i<=m;i++)
     80     {
     81         if(way[i].dis<=x)break;
     82         tot++;//tot为总长度大于mid的路径的数量 
     83         sum[way[i].x]++;sum[way[i].y]++;
     84         sum[way[i].lca]--;sum[f[way[i].lca][0]]--;//树上差分 
     85     }
     86     //getsum(1,0);
     87     
     88     for(R i=T;i>1;i--)//按时间戳从后往前,就相当于搜索树上从子节点到父节点 
     89     sum[f[dfn[i]][0]]+=sum[dfn[i]];
     90     
     91     int ma=0;
     92     for(R i=1;i<n;i++)
     93         if(sum[ed[i].x]==tot&&sum[ed[i].y]==tot)//一条边的两个端点的点权都等于tot
     94         {
     95             ma=ed[i].z;
     96             break;
     97         }
     98     if(way[1].dis-ma<=x)return true;
     99     return false;
    100 }
    101 signed main()
    102 {
    103     n=read();m=read();
    104     for(R i=1;i<n;i++)
    105     {
    106         int a=read(),b=read(),c=read();
    107         add(a,b,c);add(b,a,c);
    108         ed[i].x=a;ed[i].y=b;ed[i].z=c; 
    109     }
    110     sort(ed+1,ed+n);//先给所有边排个序,方便check的时候找最长边
    111     dfs(1,0);
    112     for(R j=1;j<=17;j++)
    113         for(R i=1;i<=n;i++)
    114             f[i][j]=f[f[i][j-1]][j-1];//我用的是倍增法求LCA 
    115     for(R i=1;i<=m;i++)
    116     {
    117         int u=read(),v=read();
    118         way[i].x=u;way[i].y=v;
    119         way[i].lca=lca(u,v);//预处理出所有路径的相关信息 
    120         way[i].dis=dis[u]+dis[v]-2*dis[way[i].lca];
    121     }
    122     sort(way+1,way+1+m);//记得排序 
    123     int l=way[1].dis-ed[1].z,r=way[1].dis;//优化一下二分,将l设置成可能的最小值,这样数据的最后一个点就不容易超时 
    124     while(l<r)
    125     {
    126         int mid=l+r>>1;
    127         if(check(mid))r=mid;
    128         else l=mid+1;
    129     }
    130     printf("%d",l);
    131     return 0;
    132 }
  • 相关阅读:
    连接服务器时通时不通的问题
    二进制包搭建k8s集群
    2、带着问题学习JVM_对象存活_垃圾回收的内容_理论_收集算法
    四、redis事务_真正实现分布式Lua_分布式锁
    四、Spring 框架中接入单机Redis的两种方式
    3、通过zkCli⼯具命令和zk 的java API两种方式创建⼀个主从模式示列
    1、带着问题学习JVM_Java为什么属于编译型+解释型的高级语言_Class类文件结构理解
    2、zookeeper安装、常用命令、API说明及简单示列(创建ZK会话及对节点进行操作、创建分布式锁、创建全局唯一ID)
    1、zk的产生、优劣、基础知识
    十三、Spring容器的原理及源码分析
  • 原文地址:https://www.cnblogs.com/ljy-endl/p/13576135.html
Copyright © 2011-2022 走看看