bzoj 2282: [Sdoi2011]消防(树的直径+二分)

41 篇文章 0 订阅

2282: [Sdoi2011]消防

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 569  Solved: 364
[ Submit][ Status][ Discuss]

Description

某个国家有n个城市,这n个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为zi(zi<=1000)。
这个国家的人对火焰有超越宇宙的热情,所以这个国家最兴旺的行业是消防业。由于政府对国民的热情忍无可忍(大量的消防经费开销)可是却又无可奈何(总统竞选的国民支持率),所以只能想尽方法提高消防能力。
现在这个国家的经费足以在一条边长度和不超过s的路径(两端都是城市)上建立消防枢纽,为了尽量提高枢纽的利用率,要求其他所有城市到这条路径的距离的最大值最小。
你受命监管这个项目,你当然需要知道应该把枢纽建立在什么位置上。

Input

输入包含n行:
第1行,两个正整数n和s,中间用一个空格隔开。其中n为城市的个数,s为路径长度的上界。设结点编号以此为1,2,……,n。
从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2 4 7”表示连接结点2与4的边的长度为7。

Output

输出包含一个非负整数,即所有城市到选择的路径的最大值,当然这个最大值必须是所有方案中最小的。

Sample Input

【样例输入1】
5 2
1 2 5
2 3 2
2 4 4
2 5 3



【样例输入2】
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3

Sample Output

【样例输出1】

5
【样例输出2】

5

HINT

对于100%的数据,n<=300000,边长小等于1000。

Source

[ Submit][ Status][ Discuss]


题解:树的直径+二分

这个题要找寻的合法的路径一定在树的直径上。为什么?哈

我们可以采用反证法。
如果整条路径与直径没有交集,那么可以从其中一点走到某条直径上,然后任意方向往直径一端走,发现那个点到达直径一端的距离一定大于从直径是一点直接到他的距离,因为直径上另一边的那一端没有选择选择路径那一部分作为直径的另一半。 
如果有交集,此路径与选定直径会在一个点岔开,对于岔开的点一定需要通过他到达直径的某一端,而如果选择直径的话就是到达该路径的一端,那么如果到达该路径的一端更长的话,我们直径一定可以更长,所以路径一定在树的直径上。

我们可以两边dfs,找出树的直径,然后从直径上的每个点向不在直径上的叶子节点便利,然后求出直径上的每个点能够达到的最远的叶子节点。整个直径的长度很有可能超过s,所以我们要考虑截取直径的一段。看到这种最大值最小的问题,首先应该会想到二分,我们二分最大值,然后考虑从直径的两端向内缩,直到不能缩为止,判断此时的长度与s的关系看是否可行。那么什么情况就不能缩了呢,就是连在这个点的最远的叶子节点(包括直径上的)的长度>二分的答案的时候就不能再缩了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 600003
using namespace std;
int point[N],next[N],v[N],len[N],pre[N],vis[N],maxn[N];
int n,dis[N],s,q[N],ins[N],ans,ansx,head,c[N],tot,cnt;
void add(int x,int y,int z)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; len[tot]=z;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; len[tot]=z;
}
void dfs(int x,int fa,int d)
{
	if (d>ans) {
		ans=d; ansx=x;
	}
	for (int i=point[x];i!=-1;i=next[i])
	 if (v[i]!=fa) {
	 	pre[v[i]]=i;
	 	dfs(v[i],x,d+len[i]);
	 }
}
void getroad(int x)
{
	while (x!=head) {
		q[++cnt]=x;  dis[cnt+1]=dis[cnt]+len[pre[x]]; vis[x]=1;
		x=v[pre[x]^1]; 
	}
	q[++cnt]=head; vis[head]=1;
}
int find(int x)
{
	for (int i=point[x];i!=-1;i=next[i])
	 if(!vis[v[i]]) {
	 	vis[v[i]]=1;
	 	int t=find(v[i]);
	 	maxn[x]=max(maxn[x],t+len[i]);
	 }
	return maxn[x];
}
bool pd(int x)
{
	for (int i=1;i<=cnt;i++) {
	  c[i]=maxn[q[i]];
	  if (c[i]>x) return false;
    }
	int l=1; int r=cnt;
	while (true) {
		c[l+1]=max(c[l]+(dis[l+1]-dis[l]),c[l+1]);
		if (c[l+1]>x||l+1>cnt) break; 
		l++;
	}
	for (int i=1;i<=cnt;i++) c[i]=maxn[q[i]];
	while (true) {
		c[r-1]=max(c[r-1],c[r]+dis[r]-dis[r-1]);
		if (c[r-1]>x||r==l) break;
		r--;
	}
	int t=dis[r]-dis[l];
	if (t<=s) return true;
    return false;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&s);
	tot=-1; int sum=0;
	memset(point,-1,sizeof(point));
	memset(next,-1,sizeof(next));
	for(int i=1;i<n;i++){
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); ins[x]++; ins[y]++; sum+=z;
	}
	dfs(1,0,0);
	head=ansx; ans=0; 
 	dfs(ansx,0,0);   
	getroad(ansx);
	for (int i=1;i<=cnt;i++)
	 maxn[q[i]]=find(q[i]);
	int l=0; int r=sum; int ans=sum;
	while (l<=r) {
		int mid=(l+r)/2;
		if (pd(mid)) ans=min(ans,mid),r=mid-1;
		else l=mid+1;
	} 
	if (ans==0) {
		int t=0;
		for (int i=1;i<=cnt;i++) t=max(t,maxn[q[i]]);
		printf("%d\n",t);
		return 0;
	}
	printf("%d\n",ans);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值