洛谷1099 & bzoj1999 树网的核(树的直径)(双指针)

题目

洛谷1099 树网的核
bzoj1999 树网的核(加强版)

题解

树的直径
首先要发现一个结论:在任意一条树的直径上求最小偏心距是一样的!
简单证明一下,自己yy吧~
这样以后就可以乱搞了,O(N^3)暴力也可以过n=300的数据。

直接上O(N)正正解!
随便搞出一条直径来,这些点分别是a1,a2,…,at。在直径上一个点al,会有一个最大的ar,使得dist[ar]-dist[al]<=s,把这部分当作树的核。
设d[i]表示当i节点为核的一部分时,以i为子树(即不到达直径上任何一个节点)的最大偏心距。
对于这段核,偏心距

CCF=\max \left\{ \max_{l<=i<=r} \left\{ d[i]\right\},dist(a1,al),dist(ar,at) \right\}

因为\max_{1<=i<l}\left\{d[i]\right\}一定不大于dist(a1,al),这是根据直径的最长性得到的。同理max_{r<i<=t}\left\{d[i]\right\}一定不大于dist(ar,at)。根据这个我们可以简化公式成

CCF=\max\left\{\max_{1<=i<=t}\left\{d[i]\right\},dist(a1,al),dist(ar,at)\right\}

这样只要用一个双指针即可解决问题。

代码

#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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值