题目
题解
树的直径
首先要发现一个结论:在任意一条树的直径上求最小偏心距是一样的!
简单证明一下,自己yy吧~
这样以后就可以乱搞了,O(N^3)暴力也可以过n=300的数据。
直接上O(N)正正解!
随便搞出一条直径来,这些点分别是a1,a2,…,at。在直径上一个点al,会有一个最大的ar,使得dist[ar]-dist[al]<=s,把这部分当作树的核。
设d[i]表示当i节点为核的一部分时,以i为子树(即不到达直径上任何一个节点)的最大偏心距。
对于这段核,偏心距
,
因为一定不大于
,这是根据直径的最长性得到的。同理
一定不大于
。根据这个我们可以简化公式成
。
这样只要用一个双指针即可解决问题。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=500010;
int n,s;
int u,v;
int vis[maxn];int T=0;
struct E{int y,c,next;}e[maxn*2];int len=0,last[maxn];
void ins(int x,int y,int c)
{
e[++len]=(E){y,c,last[x]};last[x]=len;
}
int d[maxn],son[maxn];
void dfs(int x)
{
vis[x]=T;
d[x]=0;son[x]=x;
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
if(vis[y]==T) continue;
dfs(y);
if(d[x]<d[y]+e[k].c)
{
d[x]=d[y]+e[k].c;
son[x]=son[y];
}
}
}
int lian[maxn];int tail=0;
bool isl[maxn];
int mx=0;
void dfs1(int x,int dis)//求直径外点到直径的距离
{
if(dis>mx) mx=dis;
vis[x]=T;
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
if(vis[y]==T || isl[y]) continue;
dfs1(y,dis+e[k].c);
}
}
bool dfs2(int x)//求出直径上有哪些点
{
vis[x]=T;
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
if(vis[y]==T) continue;
if(y==v)
{
isl[x]=isl[y]=true;
lian[++tail]=y;lian[++tail]=x;
T++;dfs1(y,0);
return true;
}
if(dfs2(y))
{
isl[x]=true;
lian[++tail]=x;
dfs1(y,0);
return true;
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&s);
for(int i=1;i<n;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
ins(x,y,c);ins(y,x,c);
}
T++;
dfs(1);
T++;
dfs(u=son[1]);
v=son[1];
T++;
dfs2(u);
dfs1(u,0);
int ans=1<<30;
int r=1;
for(int i=1;i<=tail;i++)
{//debug 应该讨论r+1
while(r<tail && d[lian[r+1]]-d[lian[i]]<=s) r++;//debug d的大小由根向叶子递减,所以lian中的d递增
ans=min(ans,max( mx, max( d[lian[i]]-d[v], d[u]-d[lian[r]] )));
}
printf("%d\n",ans);
return 0;
}