【BZOJ1999】树网的核,求树的直径+单调队列乱搞

传送门
思路:
很好的一道乱搞题
原来的题目我写的是 O(n3)
由于n<=500000
所以我们可以猜一些结论来减少时间复杂度

比如说每个直径都有最小偏心距,直径上每个点的偏心距可以快速处理,在直径上时[L,R]的偏心距一定小于等于[l,r]其中L<=l<=r<=R
关于直径上每个点的偏心距,有一个很好的处理方法
以这个点为根,在不经过直径的情况下遍历所有能到达的点,计算出最大距离dis
那么它的偏心距就是max(dis,到直径左端点距离,到右端点距离)
这个结论比较显然,但不是很好想到
然后就可以随便找一条直径,预处理出dis,然后一段段从前向后枚举求答案了
用单调队列维护
复杂度 O(n)
求直径跑两次bfs即可,网上有详细说明,不再赘述

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring> 
#define M 500005
using namespace std;
int n,s,tot,S,T,ans=1<<30;
int dis[M],first[M],up[M],mx[M],D[M],q[M];
bool vis[M];
int in()
{
    char ch=getchar();int t=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return t;
}
struct edge{
    int v,next,w;
}e[M<<1];
void add(int z,int x,int y)
{
    e[++tot]=(edge){y,first[x],z};first[x]=tot;
    e[++tot]=(edge){x,first[y],z};first[y]=tot;
}
int bfs(int s)
{
    queue<int>q;
    while (!q.empty()) q.pop();
    q.push(s);
    up[s]=0;
    for (;!q.empty();q.pop())
    {
        int x=q.front();
        for (int i=first[x];i;i=e[i].next)
            if (!dis[e[i].v]&&e[i].v!=s)
                dis[e[i].v]=dis[x]+e[i].w,
                q.push(e[i].v),
                up[e[i].v]=x;
    }
    int t=1;
    for (int i=2;i<=n;++i)
        if (dis[i]>dis[t]) t=i;
    return t;
}
void bfs2(int i)
{
    queue<int>q;
    while (!q.empty()) q.pop();
    q.push(i);
    for (;!q.empty();q.pop())
    {
        int x=q.front();
            for (int j=first[x];j;j=e[j].next)
            if (!vis[e[j].v]&&!D[e[j].v])
                D[e[j].v]=D[x]+e[j].w,
                q.push(e[j].v),
                mx[i]=max(mx[i],D[e[j].v]);
    }
}
main()
{
    n=in();s=in();
    for (int i=1;i<n;++i)
        add(in(),in(),in());
    S=bfs(1);
    memset(dis,0,sizeof(dis));
    T=bfs(S);
    for (int i=T;i;i=up[i]) vis[i]=1;
    for (int i=T;i;i=up[i]) bfs2(i);
    int L=T,R=T,head=1,tail=1;
    q[1]=T;
    for (;L;L=up[L])
    {
        while (up[R]&&dis[L]-dis[up[R]]<=s)
        {
            R=up[R];
            while (head<=tail&&mx[q[tail]]<=mx[R]) --tail;
            q[++tail]=R;
            ans=min(ans,max(mx[q[head]],max(dis[R]-dis[S],dis[T]-dis[L])));
        }
        if (q[head]==L) ++head;
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值