【NOIP2015/luogu2680】运输计划 二分+树上差分

原题走这里

本题求的是改造后,使得所有路线的最大值最小
一看到最大值最小,立刻想到二分答案,我们可以二分所有路线中最长的一条,设为x,判断是否可能通过改造,使得所有路线都小于等于x。问题就被转化为了判定性问题。

接着就是是判断可行性:为了判断可行性,我们要找到这样一条边,使得
1)它被所有大于x的路径包含,
2)原最长路线-本边长度<=x,即改造本边可以可以是所有路径长度都减到x以下,
如果找得到则返回真,否则返回假。

最后,我们该怎么样找到这样一条边呢?我们可以使用树上差分。
在每一次判断的开头,对于每一条长度大于x的路径(u,v)做标记:sign[u]++,sign[v]++,sign[lca[u,v]]-=2。然后按照dfs序倒序遍历,每次将本点的sign累加到父节点上,同时判断本点到父节点的边是否符合条件即可。
具体细节见代码。

代码如下:

#include <bits/stdc++.h>
using namespace std;
char buf[10000000],*pp=buf;
#define getchar() *(pp++);
inline int read()
{
    int X=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')X=X*10+ch-'0',ch=getchar();
    return X;
}
struct edge
{
    int v,l,p;
}e[600010],q[600010];
int n,m,cnt,head[300010],qhead[300010],qcnt=1,f[300010],p[300010],to[300010],sign[300010],s[300010],U[300010],V[300010],lca[300010],dfn[300010],d[300010],maxx,dfs_clock;
inline void addedge(edge *E,int *H,int &CNT,int u,int v,int l)
{
    E[++CNT]=(edge){v,l,H[u]};
    H[u]=CNT;
    E[++CNT]=(edge){u,l,H[v]};
    H[v]=CNT;
}
int find(int u)
{
    return f[u]==u?u:f[u]=find(f[u]);
}
void merge(int u,int v)
{
    f[find(u)]=find(v);
}
void Tarjan(int u)//Tarjan求LCA
{
    f[u]=u;
    dfn[++dfs_clock]=u;
    for(int i=head[u];i;i=e[i].p)
    {
        int v=e[i].v;
        if(f[v])continue;
        s[v]=s[u]+e[i].l;
        p[v]=u;
        to[v]=e[i].l;
        Tarjan(v);
        merge(v,u);
    }
    for(int i=qhead[u];i;i=q[i].p)
    {
        int v=q[i].v;
        if(f[v])
        {
            lca[i/2]=find(v);
            maxx=max(maxx,d[i/2]=s[u]+s[v]-2*s[lca[i/2]]);      
        }
    }
}
bool judge(int x)
{
    memset(sign,0,sizeof(sign));
    int num=0;
    for(int i=1;i<=m;i++)
    {
        if(d[i]>x)//大于x的路径加标记
        {
            sign[U[i]]++;
            sign[V[i]]++;
            sign[lca[i]]-=2;
            num++;
        }
    }
    for(int i=n;i;i--)//树上差分,按dfs序倒序遍历以代替递归回溯
    {
        sign[p[dfn[i]]]+=sign[dfn[i]];
        if(to[dfn[i]]>=maxx-x&&sign[dfn[i]]==num)return 1;
    }
    return 0;
}
int main()
{
    fread(buf,sizeof(char),sizeof(buf),stdin);
    n=read();
    m=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read(),l=read();
        addedge(e,head,cnt,u,v,l);
    }
    for(int i=1;i<=m;i++)
    {
        addedge(q,qhead,qcnt,U[i]=read(),V[i]=read(),0);
    }
    Tarjan(1);
    int l=0,r=maxx+1;
    while(l<r)
    {
            int mid=(l+r)/2;
            if(judge(mid))//二分
            {
                r=mid;
        }
        else
        {
            l=mid+1;
        }
    }
    cout<<l<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值