洛谷 1099 ( bzoj 1999 ) [Noip2007]Core树网的核
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1999
《算法竞赛进阶指南》346页。https://www.cnblogs.com/shenben/p/5895325.html
1.用随便一条直径算就行了。
由题知所有直径都有交点。故有公共部分。
如果分叉形状。核选在分叉后的地方,偏心距一定它是到直径的较远端点的距离。不然直径就不是最长的了。
核选在分叉前的地方,偏心距一定比上述距离短。不然直径就不是最长的了。所以选在分叉前的地方一定更优。
所有的分叉前的地方就是所有直径的公共部分。所以随便找一条直径求就行了。
2.核在不超限的情况下越长越好。所以在直径上弄两个指针,枚举核就是O(n)的了。
3.max( max( dis[k] ) , dis[ s~i ] , dis[ j~t ] ) ( k是 i ~ j 连出去的子树中的点 ) <==> max( max( dis[k] ) , dis[ s~i ] , dis[ j~t ] ) ( k是所有的点 )
之所以等价是因为对于一段核,直径上别的部分连出去的子树中的点到直径的距离一定小于第一个式子中的三个部分。不然直径就不是最长的了。
然后 max( dis[k] ) ( k是所有的点 ) 就是一个定值了!只要找一下上式后两个部分的min,最后和max( dis[k] )取个max就行了。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=5e5+5; int n,s,head[N],xnt,fa[N],dis[N],ans,l0,l1; bool vis[N]; struct Edge{ int next,to,w; Edge(int n=0,int t=0,int w=0):next(n),to(t),w(w) {} }edge[N<<1]; int rdn() { int ret=0,fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=-1;ch=getchar();} while(ch>='0'&&ch<='9')(ret*=10)+=ch-'0',ch=getchar(); return ret*fx; } void add(int x,int y,int z) { edge[++xnt]=Edge(head[x],y,z);head[x]=xnt; edge[++xnt]=Edge(head[y],x,z);head[y]=xnt; } void dfs(int cr,int f) { for(int i=head[cr],v;i;i=edge[i].next) if((v=edge[i].to)!=f&&!vis[v]) { dis[v]=dis[cr]+edge[i].w; fa[v]=cr;dfs(v,cr); } } int main() { n=rdn();s=rdn();int x,y,z; for(int i=1;i<n;i++) { x=rdn();y=rdn();z=rdn(); add(x,y,z); } dfs(1,0);l0=1; for(int i=1;i<=n;i++)if(dis[i]>dis[l0])l0=i; dis[l0]=0;fa[l0]=0;dfs(l0,0); for(int i=1;i<=n;i++)if(dis[i]>dis[l1])l1=i; ans=0x7fffffff;int j=l1; for(int i=l1;i;i=fa[i]) { vis[i]=1; while(j&&dis[i]-dis[fa[j]]<=s)j=fa[j]; ans=min(ans,max(dis[l1]-dis[i],dis[j])); } for(int i=1;i<=n;i++)if(vis[i])dis[i]=0,dfs(i,0); for(int i=1;i<=n;i++)ans=max(ans,dis[i]); printf("%d",ans); return 0; }