bzoj1999 (洛谷1099) 树网的核——dfs

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1999

    https://www.luogu.org/problemnew/show/P1099

“分析性质,O(n)扫描”

看了半天才懂...发现自己对树的直径的相关知识太不熟了...

这篇博客的讲解很详细:https://www.cnblogs.com/shenben/p/5895325.html

说一下自己的理解:

1.每个直径对答案的贡献是相同的;

  因为所有直径都相交,所以不妨考虑公共部分和分叉部分;

  会发现分叉部分的答案不如公共部分优,因为我们要取最小的偏心距,而分叉部分的偏心距一定会覆盖公共部分;

  所以只需要找出一条直径来做;

2.一条直径上找最小偏心距,一定在“核”最长的时候找;

  这个很容易想到,因为长“核”一定不会劣于短“核”;

3.对于一个“核”,有三个值可以更新它的偏心距:

  ①直径上的两端到“核”的两端的两个距离;

  ②“核”上所有点到直径外子树的最长距离;

  其中的①,许多“核”会重复求同样的值,所以干脆把它扩展到整条直径上的点到直径外子树的最大距离,求一遍即可;

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const maxn=500005;
int n,s,head[maxn],ct,dis[maxn],fa[maxn],l,l2;
bool vis[maxn];
struct N{
    int to,next,w;
    N(int t=0,int n=0,int w=0):to(t),next(n),w(w) {}
}edge[maxn<<1];
void add(int x,int y,int z){edge[++ct]=N(y,head[x],z); head[x]=ct;}
int rd()
{
    int ret=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
void dfs(int x)
{
    for(int i=head[x];i;i=edge[i].next)
    {
        int u=edge[i].to;
        if(vis[u]||u==fa[x])continue;
        dis[u]=dis[x]+edge[i].w;
        fa[u]=x; dfs(u);
    }
}
int main()
{
    n=rd(); s=rd();
    for(int i=1,x,y,z;i<n;i++)
    {
        x=rd(); y=rd(); z=rd();
        add(x,y,z); add(y,x,z);
    }
    l=1; l2=1;
    dis[l]=0; fa[l]=0; dfs(l);
    for(int i=1;i<=n;i++)
        if(dis[i]>dis[l])l=i;
    dis[l]=0; fa[l]=0; dfs(l);
    for(int i=1;i<=n;i++)
        if(dis[i]>dis[l2])l2=i;
    int ans=0x3f3f3f3f,j=l2;
    for(int i=l2;i;i=fa[i])
    {
        while(fa[j]&&dis[i]-dis[fa[j]]<=s)j=fa[j];
        ans=min(ans,max(dis[j],dis[l2]-dis[i]));//min,max
    }
    for(int i=l2;i;i=fa[i])vis[i]=1;
    for(int i=l2;i;i=fa[i])dis[i]=0,dfs(i);
    for(int i=1;i<=n;i++)ans=max(ans,dis[i]);//max
    printf("%d",ans);
    return 0;
}

 

posted @ 2018-06-19 18:42  Zinn  阅读(133)  评论(0编辑  收藏  举报