【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 }
原文地址:https://www.cnblogs.com/ljy-endl/p/13576135.html