2282: [Sdoi2011]消防
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 569 Solved: 364
[ Submit][ Status][ Discuss]
Description
Input
输入包含n行:
第1行,两个正整数n和s,中间用一个空格隔开。其中n为城市的个数,s为路径长度的上界。设结点编号以此为1,2,……,n。
从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2 4 7”表示连接结点2与4的边的长度为7。
Output
输出包含一个非负整数,即所有城市到选择的路径的最大值,当然这个最大值必须是所有方案中最小的。
Sample Input
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
5
【样例输出2】
5
HINT
对于100%的数据,n<=300000,边长小等于1000。
Source
题解:树的直径+二分
这个题要找寻的合法的路径一定在树的直径上。为什么?哈
我们可以采用反证法。
如果整条路径与直径没有交集,那么可以从其中一点走到某条直径上,然后任意方向往直径一端走,发现那个点到达直径一端的距离一定大于从直径是一点直接到他的距离,因为直径上另一边的那一端没有选择选择路径那一部分作为直径的另一半。
如果有交集,此路径与选定直径会在一个点岔开,对于岔开的点一定需要通过他到达直径的某一端,而如果选择直径的话就是到达该路径的一端,那么如果到达该路径的一端更长的话,我们直径一定可以更长,所以路径一定在树的直径上。
我们可以两边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);
}