队列数组模拟+树的直径+二分+贪心 AcWing351

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queue

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}
int n , s, st, ed;
int sum = 0;
int head[500005], cnt;
struct e{
    int t ,w , next;
}edge[1000005];
void add(int f , int t, int w)
{
    edge[cnt].t = t;
    edge[cnt].w = w;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int vis[500005], path[500005];
int dis[500005];
int show[500005], q[500005];
vector <pair<int,int> > pt;
int bfs(int root)
{
    int ss = 0 , ee = 0;
    mem(dis,INF);
    mem(path,0);
    dis[root] = 0;
    q[0] = root;
    int len = 0 , index = 0;
    while(ss <= ee)
    {
        int no = q[ss ++];
        for(int i = head[no] ; i != -1; i = edge[i].next)
        {
            int v = edge[i].t, w = edge[i].w;
            if(dis[v] > dis[no] + w)
            {
                path[v] = no;
                dis[v] = dis[no] + w;
                if(dis[v] > len) len = dis[v], index = v;
                q[++ ee] = v;
            }
        }
    }
    return index;
}
void GetPath(int u)
{
    while(u)
    {
        pt.push_back(mk(u,dis[u]));
        u = path[u];
    }
    reverse(pt.begin(),pt.end());
}
int check_bfs(int u)
{
    int len = 0;
    int ss = 0 , ee = 0;
    q[0] = u;
    while(ss <= ee)
    {
        int no = q[ss ++];
        len = max(len,dis[no]);
        for(int i = head[no] ; i != - 1; i = edge[i].next)
        {
            int v = edge[i].t , w = edge[i].w;
            if(!show[v])
            {
                show[v] = 1;
                dis[v] = dis[no] + w;
                q[++ee] = v;
            }
        }
    }
    return len;
}
bool check(int x)
{
    //获得离端点最远且满足偏心距位置(贪心)
    int l = 0, r = pt.size()-1;
    while (l + 1 < pt.size() && pt[l + 1].second <= x) l ++ ;
    while (r - 1 >= 0 && pt.back().second - pt[r - 1].second <= x) r -- ;
    if(l > r) return true;//如果l,r交叉说明满足 因为l到端点的距离一定大于等于分叉子树最远点到l的距离(如果小于,说明直径不对)
    if(pt[r].se - pt[l].se > s) return false;// 两端点最近点的距离如果大于题目要求,不满足
    mem(show,0);
    mem(dis,0);
    //标记直径上的点,距离一定小于题目要求,并且,只需要求主干外其他点到主干路径的最小值
    for(auto it : pt)
        show[it.fi] = 1;
    for(int i = l ; i <= r; i ++)
        if(check_bfs(pt[i].fi) > x) //bfs检测,以主干上某点出发的最大距离
            return false;
    return true;

}

int main(void)
{
    mem(head,-1);
    scanf("%d %d",&n , &s);
    for(int i = 1 ; i < n ; i ++)
    {
        int u , v,  w;
        scanf("%d %d %d",&u ,&v, &w);
        add(u,v,w);
        add(v,u,w);
        sum += w;
    }
    //获得直径
    st = bfs(1);
    ed = bfs(st);
    //获得直径上各点以及到达该点的距离
    GetPath(ed);
    //二分偏心距
    int l = 0, r = sum;
    for(int i = 1 ; i <= 50 ; i ++)
    {
        int mid = (l + r) / 2;
        if(check(mid))
            r = mid;
        else
            l = mid;
    }
    printf("%d",r);

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值