OI题解 - NOIP2018 赛道修建 | Lucky_Glass's Blog
0%

OI题解 - NOIP2018 赛道修建

额……考试的时候大概猜到正解,但是时间不够了,不敢写,就写了骗分QwQ
现在把坑填好了~


题目

(Copy from 洛谷)

题目描述

C 城将要举办一系列的赛车比赛。在比赛前,需要在城内修建 $m$ 条赛道。
C 城一共有 $n$ 个路口,这些路口编号为 $1,2,…,n$,有 $n-1$ 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口。其中,第 $i$ 条道路连接的两个路口编号为 $a_i$ 和 $b_i$,该道路的长度为 $l_i$ 。借助这 $n-1$ 条道路,从任何一个路口出发都能到达其他所有的路口。
一条赛道是一组互不相同的道路 $e_1,e_2,…,e_k$,满足可以从某个路口出发,依次经过 道路 $e_1,e_2,…,e_k$(每条道路经过一次,不允许调头)到达另一个路口。一条赛道的长度等于经过的各道路的长度之和。为保证安全,要求每条道路至多被一条赛道经过。
目前赛道修建的方案尚未确定。你的任务是设计一种赛道修建的方案,使得修建的 $m$ 条赛道中长度最小的赛道长度最大(即 $m$ 条赛道中最短赛道的长度尽可能大)

输入输出格式

输入格式:

输入文件第一行包含两个由空格分隔的正整数 $n,m$,分别表示路口数及需要修建的 赛道数。
接下来 $n-1$ 行,第 ii 行包含三个正整数 $a_i,b_i,l_i$ ,表示第 $i$ 条适合于修建赛道的道 路连接的两个路口编号及道路长度。保证任意两个路口均可通过这 $n-1$ 条道路相互到达。每行中相邻两数之间均由一个空格分隔。

输出格式:

输出共一行,包含一个整数,表示长度最小的赛道长度的最大值。


解析

首先题目非常重要——这是一个树形结构,而且满足单调性(即如果答案为x,则>x的值都不能符合条件,<x的值都符合条件)。
那么根据单调性就可以二分了,还是比较容易想到二分答案。然后就是检验的问题了……

如果大家做了“菊花图”(就是从一个顶点伸出去很多条边)的部分数据,就应该知道有一个贪心的性质——对于一个点u,如果连接 u 和 u的一个儿子 的边的长度大于等于答案,那就把这条边割开,单独形成一条路径;接下来考虑长度比答案小的边,那么我们尽量地将长度大的边与较小边匹配形成一条路径。这样的话我们就可以写出来一段代码(AC的):

1
2
3
4
5
6
7
8
9
//edg[]是u与u的儿子的边的长度(都不超过二分出来的答案)
sort(edg,edg+n);
int res=0;
for(int l=0,r=n;l<r;r--){
while(l<r && edg[l]+edg[r]<ans) l++;
if(l>=r) break;
res++; //新构成的路径
l++;
}

我们不妨考虑每一个点 u 及其儿子 v[] ——形成路径的情况无非是①加上u->v[i]的边形成一个路径;②加上u->v[i]和u->v[j]的边形成一个路径;③加上u->v[i]的边然后再与u的祖先相连。
不难想到我们可以把每棵子树都看成一个菊花图做一次贪心。
但是有上述的情况③,这样的话就不能直接看边权了,我们可以让 dp[u] 表示在点u的子树以u为端点的路径的长度。然后考虑子树u中u的一个儿子v,如果 dp[v]+(u->v的边长) 满足答案,就将已经构成的路径个数cnt++;否则将 dp[v]+(u->v的边长) 储存到use[u]中。最后把use[u]中的边按菊花图的方法做一次贪心,就可以得到能够构成的路径条数的最大值:
1
2
3
4
5
6
for(int l=0,r=use[u].size()-1;r>=0;r--){
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l>=r) break;
mat++;l++;
}
fans[u]+=mat; //储存答案

贪心的想,在这里一定要尽可能多的构成路径,但是在这个条件下将尽可能长的一个没必要匹配的路径留下来继续向u的祖先连接;很明显这也是存在单调性的——如果选择一条长度为X的路径“留下来”可以得到最大构成的路径数,那么长度小于X的路径就一定也可以得到最大路径数。
1
2
3
4
5
6
while(l+1<r){
int mid=(l+r)>>1;
if(Check(u,mid,use[u].size(),num)==mat) l=mid;
else r=mid;
}
dp[u]=use[u][l];

这样就可以检验了,即看能够得到的路径条数是否能够满足要求。
(不知道大家看懂没啊……有什么问题的话还是直接在文末的作者邮箱里面问吧QwQ)


源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*Lucky_Glass*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50000;
struct EDGE{
int to,nxt;ll dis;
EDGE(){}
EDGE(int _to,int _nxt,ll _dis):to(_to),nxt(_nxt),dis(_dis){}
}edg[N*2+5];
int adj[N+5],edgtot;
void AddEdge(int u,int v,ll dis){
edg[++edgtot]=EDGE(v,adj[u],dis);
adj[u]=edgtot;
}
int n,m;
vector< int > use[N+5];
int dp[N+5],fans[N+5];
int Check(int u,int tag,int siz,ll num){
int res=0;
for(int l=0,r=siz-1;r;r--){
if(r==tag) r--;
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l==tag) l++;
if(l>=r) break;
res++;l++;
}
return res;
}
void DFS(int u,int pre,ll num){
dp[u]=fans[u]=0;use[u].clear();
for(int i=adj[u];i;i=edg[i].nxt){
int v=edg[i].to;ll dis=edg[i].dis;
if(v==pre) continue;
DFS(v,u,num);
dp[v]+=dis;
if(dp[v]>=num) fans[u]++;
else use[u].push_back(dp[v]);
}
sort(use[u].begin(),use[u].end());
int mat=0;
for(int l=0,r=use[u].size()-1;r>=0;r--){
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l>=r) break;
mat++;l++;
}
fans[u]+=mat;
if(mat*2==use[u].size()) return;
int l=0,r=use[u].size();
while(l+1<r){
int mid=(l+r)>>1;
if(Check(u,mid,use[u].size(),num)==mat) l=mid;
else r=mid;
}
dp[u]=use[u][l];
}
bool Check(ll num){
DFS(1,-1,num);
int res=0;
for(int i=1;i<=n;i++)
res+=fans[i];
return res>=m;
}
int main(){
scanf("%d%d",&n,&m);
ll l=0,r=0;
for(int i=1;i<n;i++){
int u,v;ll dis;scanf("%d%d%lld",&u,&v,&dis);
AddEdge(u,v,dis);AddEdge(v,u,dis);
r+=dis;
}
r/=1ll*m;
while(l+1<r){
ll mid=(l+r)>>1;
if(Check(mid)) l=mid;
else r=mid;
}
if(Check(r)) printf("%lld\n",r);
else printf("%lld\n",l);
return 0;
}

The End

Thanks for reading!

Email: lucky_glass@foxmail.com ,欢迎提问~