UOJ 150 [NOIP2015]运输计划

二分+树链剖分求LCA+差分

要求最远的值最小,于是我们可以考虑二分答案。

对于每一个二分出来的lim,我们只需要判断所有路径(指运输计划里面所有的路径)中长度大于lim的所有路径是否存在一条公共边,使得最长路径减去它可以小于等于lim(如果最长的可以,那么短的也肯定可以)

那么怎么求这条边?我刚开始的想法是树剖+线段树,对每一条超过lim的路径上的边的都记+1,然后暴力枚举边来找公共边。但是这样是 O(nlog3n) 不太科学。。。

实际上可以根本不线段树,维护一个树上差分数组f即可,值也是照样加。。。预处理所有LCA,复杂度就是 O(nlogn)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300005
#define reg register
using namespace std;
struct edge{int next,to,val;}e[N<<1];int ecnt;
int n, m, last[N], fa[N], dep[N], siz[N], top[N], son[N], u[N], v[N], d[N], dis[N], Lca[N], f[N], mxlen, mx, tot, b[N];
int in()
{
    reg int r=0;
    reg char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')r=r*10+c-'0',c=getchar();
    return r;
}
void addedge(int a, int b, int c)
{
    e[++ecnt]=(edge){last[a],b,c};
    last[a]=ecnt;
}
void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    siz[x]=1;
    for(int i = last[x]; i; i=e[i].next)
    {
        int y=e[i].to;
        if(y==fa[x])continue;
        fa[y]=x;
        dis[y]=dis[x]+e[i].val;
        dfs1(y);
        siz[x]+=siz[y];
        if(siz[y]>siz[son[x]])
            son[x]=y;
    }
}
void dfs2(int x)
{
    if(son[fa[x]]==x)top[x]=top[fa[x]];
    else top[x]=x;
    for(int i = last[x]; i; i=e[i].next)
    {
        int y=e[i].to;
        if(y!=fa[x])
            dfs2(y);
    }
}
int lca(int a, int b)
{
    while(top[a]!=top[b])
    {
        if(dep[top[a]]>dep[top[b]])
            a=fa[top[a]];
        else b=fa[top[b]];
    }
    return dep[a]>dep[b]?b:a;
}
void make(int i)
{
    f[u[i]]++;
    f[v[i]]++;
    f[Lca[i]]-=2;
}
void find(int x)
{
    b[x]=f[x];
    for(int i = last[x]; i; i=e[i].next)
    {
        int y=e[i].to;
        if(y==fa[x])continue;
        find(y);
        b[x]+=b[y];
        if(b[y]==tot)
            mx=max(mx,e[i].val);
    }
}
bool check(int lim)
{
    tot=0;
    memset(f,0,sizeof(f));
    for(int i = 1; i <= m; i++)
        if(d[i]>lim)
        {
            ++tot;
            make(i);
        }
    mx=0;
    find(1);
    return mxlen-mx<=lim;
}
int main()
{
    n=in(), m=in();
    for(int i = 1; i < n; i++)
    {
        int a=in(), b=in(), t=in();
        addedge(a,b,t);
        addedge(b,a,t);
    } 
    dfs1(1);
    dfs2(1);
    for(int i = 1; i <= m; i++)
    {
        u[i]=in(), v[i]=in(); 
        d[i]=dis[u[i]]+dis[v[i]]-2*dis[Lca[i]=lca(u[i],v[i])];
        mxlen=max(mxlen,d[i]);
    }   
    int l = max(0,mxlen-1000), r = mxlen;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    printf("%d\n",l);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值