洛谷P1099&Noip 2007提高-树网的核(树直径上的尺取)

题目链接:https://www.luogu.com.cn/problem/P1099
博客园食用链接:https://www.cnblogs.com/lonely-wind-/p/13456497.html

题目描述

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),vV}

任务:对于给定的树网 T = ( V , E , W ) T=(V, E, W) T=(V,E,W)和非负整数 s s s,求一个路径 F F F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过 ss(可以等于 s s s),使偏心距 E C C ( F ) ECC(F) ECC(F) 最小。我们称这个路径为树网 T = ( V , E , W ) T=(V, E, W) T=(V,E,W) 的核(Core)。必要时, F F F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。

下面的图给出了树网的一个实例。图中, A − B A-B AB A − C A-C AC 是两条直径,长度均为 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,中间用一个空格隔开。其中 nn 为树网结点的个数, 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

输出格式
一个非负整数,为指定意义下的最小偏心距。

输入输出样例
输入
5 2
1 2 5
2 3 2
2 4 4
2 5 3
输出
5

输入
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
输出
5

说明/提示
对于 40 % 40\% 40% 的数据,保证 n ≤ 15 n \le 15 n15
对于 70 % 70\% 70% 的数据,保证 n ≤ 80 n \le 80 n80
对于 100 % 100\% 100%的数据,保证 n ≤ 300 , 0 ≤ s ≤ 1 0 3 , 1 ≤ u , v ≤ n , 1 ≤ w ≤ 1 0 3 n \le 300,0\le s\le10^3,1 \leq u, v \leq n,1 \leq w \leq 10^3 n3000s103,1u,vn1w103

emmm,这可能是为数不多的需要翻译的中文题。。。说点阳间的话就是:你需要在树的直径上找一段长为 s s s的路径,使得距离 s s s最远的的点距离 s s s最近。至于点到路径的距离是个什么鬼?实际上也就是点到点的距离,如果s可以覆盖两个点,那么你就需要计算一下离这两个点最远的几个点的距离,然后取个最大值。如果只能覆盖半条边,那么它只能拿端点计算。

那么当什么都不做的时候我们知道树的直径是最长的一条链,所以我们的s需要在这上面取来使得这个距离变小,那么在直径上取路径的话有两种情况,一种是直径的端点离这个路径最远,还有一种情况是非直径的点离这条路径最远。

那么我们先来处理第一种情况,我们知道树的直径的做法可以用两遍dfs求,这样不仅求出了直径的两个端点,还求出了一个端点到所有点的距离 d i s t [ v ] dist[v] dist[v],那么我们知道,对于一棵树而言,每个节点的父亲只有一个,那么也就是说我们可以根据 f a t h e r father father从直径的终点来推到直径的起点,既然已经知道了这条链了,那么我们就可以直接尺取了, i , j i,j i,j从终点开始, j j j一直往上爬,一旦 d i s ( i , j ) > s dis(i,j)>s dis(i,j)>s那么 i i i就一直往上爬。同时在取的过程中我们取一下他们离直径两个端点最大值的最小值。其代码片段如下:

void dfs(int x,int fa)
{
	father[x]=fa;
	for (auto v:g[x]){
		if (v.first==fa) continue;
		dis[v.first]=dis[x]+v.second;
		if (dis[v.first]>=d) {d=dis[v.first]; pt=v.first;}
		dfs(v.first,x);
	}
}
/*********/
dfs(1,-1);
memset(dis,0,sizeof dis);
st=pt;
dfs(pt,-1);
ed=pt;
int ans=inf;
for (int i=ed,j=ed; (i!=-1) && (j!=-1); j=father[j]) {
	while (dis[i]-dis[j]>s && i!=-1) i=father[i];
	ans=min(ans,max(dis[j],dis[ed]-dis[i]));
}

接下来就是考虑第二种情况了,我们直接取不经过直径离直径上最远的点的最大值就好了,这个答案可以直接对直径上的每个点进行一次dfs就好了,不过在这之前由于不能经过直径,所以我们要对直径上的点打上标记:

int dfs_dis(int x,int fa)
{
	int dist=0;
	for (auto v:g[x]){
		if (v.first==fa) continue;
		if (vis[v.first]) continue;
		dist=max(dist,dfs_dis(v.first,x)+v.second);
	}
	return dist;
}
/*************************/
for (int i=ed; i!=-1; i=father[i]) vis[i]=1;
for (int i=ed; i!=-1; i=father[i]) {
	int p=dfs_dis(i,-1);
	ans=max(p,ans);
}

于是此题就愉快地结束了!!

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

#define debug printf("@#$#@$2\n")
#define mk make_pair
const int mac=1e3+10;
const int inf=1e9+10;

vector<pair<int,int> >g[mac];
int dis[mac],d=0,pt,st,ed;
int father[mac],vis[mac];

void dfs(int x,int fa)
{
	father[x]=fa;
	for (auto v:g[x]){
		if (v.first==fa) continue;
		dis[v.first]=dis[x]+v.second;
		if (dis[v.first]>=d) {d=dis[v.first]; pt=v.first;}
		dfs(v.first,x);
	}
}

int dfs_dis(int x,int fa)
{
	int dist=0;
	for (auto v:g[x]){
		if (v.first==fa) continue;
		if (vis[v.first]) continue;
		dist=max(dist,dfs_dis(v.first,x)+v.second);
	}
	return dist;
}

int main(int argc, char const *argv[])
{
	int n,s;
	scanf ("%d%d",&n,&s);
	for (int i=1; i<n; i++){
		int u,v,w;
		scanf ("%d%d%d",&u,&v,&w);
		g[u].push_back(mk(v,w)); g[v].push_back(mk(u,w));
	}
	dfs(1,-1);
	memset(dis,0,sizeof dis);
	st=pt;
	dfs(pt,-1);
	ed=pt;
	int ans=inf;
	for (int i=ed,j=ed; (i!=-1) && (j!=-1); j=father[j]){
		while (dis[i]-dis[j]>s && i!=-1) i=father[i];
		ans=min(ans,max(dis[j],dis[ed]-dis[i]));
	}
	for (int i=ed; i!=-1; i=father[i]) vis[i]=1;
	for (int i=ed; i!=-1; i=father[i]){
		int p=dfs_dis(i,-1);
		ans=max(p,ans);
	}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值