[NOIP2007 提高组] 树网的核
题目描述
设
T
=
(
V
,
E
,
W
)
T=(V,E,W)
T=(V,E,W) 是一个无圈且连通的无向图(也称为无根树),每条边都有正整数的权,我们称
T
T
T 为树网(treenetwork
),其中
V
V
V,
E
E
E 分别表示结点与边的集合,
W
W
W 表示各边长度的集合,并设
T
T
T 有
n
n
n 个结点。
路径:树网中任何两结点
a
a
a,
b
b
b 都存在唯一的一条简单路径,用
d
(
a
,
b
)
d(a, b)
d(a,b) 表示以
a
,
b
a, b
a,b 为端点的路径的长度,它是该路径上各边长度之和。我们称
d
(
a
,
b
)
d(a, b)
d(a,b) 为
a
,
b
a, b
a,b 两结点间的距离。
D ( v , P ) = min { d ( v , u ) } D(v, P)=\min\{d(v, u)\} D(v,P)=min{d(v,u)}, u u u 为路径 P P P 上的结点。
树网的直径:树网中最长的路径成为树网的直径。对于给定的树网 T T T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距 E C C ( F ) \mathrm{ECC}(F) ECC(F):树网 T T T 中距路径 F F F 最远的结点到路径 F F F 的距离,即
E C C ( F ) = max { D ( v , F ) , v ∈ V } \mathrm{ECC}(F)=\max\{D(v, F),v \in V\} ECC(F)=max{D(v,F),v∈V}
任务:对于给定的树网
T
=
(
V
,
E
,
W
)
T=(V, E, W)
T=(V,E,W) 和非负整数
s
s
s,求一个路径
F
F
F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过
s
s
s(可以等于
s
s
s),使偏心距
E
C
C
(
F
)
\mathrm{ECC}(F)
ECC(F) 最小。我们称这个路径为树网
T
=
(
V
,
E
,
W
)
T=(V, E, W)
T=(V,E,W) 的核(Core
)。必要时,
F
F
F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
下面的图给出了树网的一个实例。图中,
A
−
B
A-B
A−B 与
A
−
C
A-C
A−C 是两条直径,长度均为
20
20
20。点
W
W
W 是树网的中心,
E
F
EF
EF 边的长度为
5
5
5。如果指定
s
=
11
s=11
s=11,则树网的核为路径DEFG
(也可以取为路径DEF
),偏心距为
8
8
8。如果指定
s
=
0
s=0
s=0(或
s
=
1
s=1
s=1、
s
=
2
s=2
s=2),则树网的核为结点
F
F
F,偏心距为
12
12
12。
输入格式
共 n n n 行。
第 1 1 1 行,两个正整数 n n n 和 s s s,中间用一个空格隔开。其中 n n n 为树网结点的个数, s s s 为树网的核的长度的上界。设结点编号以此为 1 , 2 … , n 1,2\dots,n 1,2…,n。
从第
2
2
2 行到第
n
n
n 行,每行给出
3
3
3 个用空格隔开的正整数
u
,
v
,
w
u, v, w
u,v,w,依次表示每一条边的两个端点编号和长度。例如,2 4 7
表示连接结点
2
2
2 与
4
4
4 的边的长度为
7
7
7。
输出格式
一个非负整数,为指定意义下的最小偏心距。
样例 #1
样例输入 #1
5 2
1 2 5
2 3 2
2 4 4
2 5 3
样例输出 #1
5
样例 #2
样例输入 #2
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
样例输出 #2
5
提示
- 对于 40 % 40\% 40% 的数据,保证 n ≤ 15 n \le 15 n≤15。
- 对于 70 % 70\% 70% 的数据,保证 n ≤ 80 n \le 80 n≤80。
- 对于 100 % 100\% 100% 的数据,保证 n ≤ 300 n \le 300 n≤300, 0 ≤ s ≤ 1 0 3 0\le s\le10^3 0≤s≤103, 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n, 1 ≤ w ≤ 1 0 3 1 \leq w \leq 10^3 1≤w≤103。
步骤:
1.找直径 -->【两遍DFS法】
2.找 距那个线段(题中路径F)的最小 最大值。–>【尺取法(即“双指针法”)枚举路径F,听说还用到了“单调队列”qwq( sorry 窝木有看出来哪部分是单调队列】
这里以问答的方式记录下我的问题过程(煎熬…):
-
双指针那里没太看懂…
就是保证长度小于等于s, 如果大于了 ,就右指针左移,这样左指针就可以继续左移,直到取完所有的情况。 -
“计算直径上每个点可以访问的最远距离” 那里 ,dfs(i, fa[i]) 我感觉有点混 它的 i 和fa[i] 明明是从后面开始搜的…
dfs就是顺着一条链往下搜,fa就是上一个搜到的点
(因为用了两遍dfs 后面开始的那个B点 就相当于最末端了 然后就一路向上跳) -
为什么dis[1]=1啊?dis[v]=dis[u]+1 的 那dis[1]=1也都加在后面了嘛?不会有影响吗?为什么要初始化为1啊?一开始就是自己 干嘛就有了1的距离啊?
dis是别的点到它的距离 他选的点是0,所以dis[1]=1;哦哦,dfs(1,0) ,0是fa。 -
为什么前面求了 ans 后面还要求一遍ans啊?
不一定只有一条直径,如果一个点同时在两条直径呢?
那样的话 长度一定相同啊?如果不同 不就两两长的组成一个更长的直径了嘛?
不一定
哦哦 中间连结的是线段不是点
蓝色左端点离绿色更远 但是两条都是直径,emmm,蓝色可能同时在两条直径上。
还有就是这里要理解dis[i]的含义,是 i 点到直径端点【第一遍dfs是端点A, 第二遍dfs是端点B】的距离,所以 dis[j] - dis[i] 就是 i~j 的距离,对于线段F: [ i, j] ,dis[B]-dis[i] 是i~B的距离,dis[i] 是A~i 的距离。dis[i]=0应该是清空上一步的…qwq
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+6;
int n,s,dis[N],fa[N],flag[N],far,ans=2e9;
struct edge{
int to,w;
};
vector<edge> G[N];
void dfs(int u,int f){
fa[u]=f;
if(dis[u]>dis[far])far=u;//记录最远的距离
for(int i=0;i<G[u].size();i++){
int v=G[u][i].to, w=G[u][i].w;
if(v==f||flag[v])continue;
dis[v]=dis[u]+w;
dfs(v,u);
}
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>s;
for(int i=2,u,v,w;i<=n;i++){
cin>>u>>v>>w;
G[u].push_back((edge){v,w});
G[v].push_back((edge){u,w});
}
int A,B; //直径的两端
dis[1]=1;dfs(1,0);A=far; //第一次dfs
dis[far]=0;dfs(far,0);B=far; //第二次dfs
for(int i=B,j=B; i; i=fa[i]){
while(dis[j]-dis[i]>s)j=fa[j]; //双指针法(尺取法)
int x=max(dis[B]-dis[j], dis[i]);
ans=min(ans,x);
}
for(int i=B; i; i=fa[i])flag[i]=1; //直径上的点跳过
for(int i=B; i; i=fa[i]){
dis[i]=0;
dfs(i,fa[i]); //计算直径上每个点可以访问的最远距离 (可以有多条直径
}
for(int i=1;i<=n;i++)
ans=max(ans,dis[i]); //得到所有点可以访问最远距离的最大值
cout<<ans;
return 0;
}
复杂度:O(n)