【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%的数据,100≤n≤300000,1≤m≤300000
请注意常数因子带来的程序效率上的影响。
阅读完题目我们发现,本题是一道求最大值最小的问题,那么考虑二分花费的时间。
在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 }