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

题目链接:https://www.luogu.com.cn/problem/P1099
CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107874674

题目描述

\(T=(V,E,W)\) 是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称 \(T\) 为树网(treenetwork),其中 \(V\)\(E\) 分别表示结点与边的集合,\(W\) 表示各边长度的集合,并设 \(T\)\(n\) 个结点。

路径:树网中任何两结点 \(a\)\(b\) 都存在唯一的一条简单路径,用 \(d(a, b)\) 表示以 \(a, b\) 为端点的路径的长度,它是该路径上各边长度之和。我们称 \(d(a,b)\)\(a, b\) 两结点间的距离。

\(D(v, P)=\min\{d(v, u)\}\), \(u\) 为路径\(P\) 上的结点。

树网的直径:树网中最长的路径成为树网的直径。对于给定的树网 \(T\),直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。

偏心距 \(\mathrm{ECC}(F)\):树网 \(T\) 中距路径 \(F\) 最远的结点到路径 \(F\) 的距离,即

\(\mathrm{ECC}(F)=\max\{d(v, F),v \in V\}\)

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

下面的图给出了树网的一个实例。图中,\(A-B\)\(A-C\) 是两条直径,长度均为 \(20\)。点\(W\) 是树网的中心,\(EF\) 边的长度为 \(5\)。如果指定 \(s=11\),则树网的核为路径DEFG(也可以取为路径DEF),偏心距为 \(8\)。如果指定 \(s=0\)(或 \(s=1\)\(s=2\)),则树网的核为结点 \(F\),偏心距为 \(12\)


输入格式
\(n\) 行。

\(1\) 行,两个正整数 \(n\)\(s\),中间用一个空格隔开。其中 nn 为树网结点的个数,\(s\) 为树网的核的长度的上界。设结点编号以此为 \(1,2\dots,n\)

从第 \(2\) 行到第 \(n\) 行,每行给出 \(3\) 个用空格隔开的正整数 \(u, v, w\),依次表示每一条边的两个端点编号和长度。例如,2 4 7 表示连接结点 \(2\)\(4\) 的边的长度为 \(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\%\) 的数据,保证 \(n \le 15\)
对于 \(70\%\) 的数据,保证 \(n \le 80\)
对于 \(100\%\)的数据,保证 \(n \le 300,0\le s\le10^3,1 \leq u, v \leq n,1 \leq w \leq 10^3\)

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

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

那么我们先来处理第一种情况,我们知道树的直径的做法可以用两遍dfs求,这样不仅求出了直径的两个端点,还求出了一个端点到所有点的距离\(dist[v]\),那么我们知道,对于一棵树而言,每个节点的父亲只有一个,那么也就是说我们可以根据\(father\)从直径的终点来推到直径的起点,既然已经知道了这条链了,那么我们就可以直接尺取了,\(i,j\)从终点开始,\(j\)一直往上爬,一旦\(dis(i,j)>s\)那么\(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;
}
posted @ 2020-08-08 09:46  lonely_wind  阅读(144)  评论(0编辑  收藏  举报