zoukankan      html  css  js  c++  java
  • P2680 运输计划

    传送门

    十分显然完成工作的时间和航耗时最长的运输计划有关

    所以题目意思就是要求最大值最小

    所以可以想到二分

    把所有大于mid时间的航线打上标记,显然删边只能在所有这些航线的公共路径上

    要如何快速打标记是个问题

    二分已经有一个log,所以只能承受O(n)的判断

    如果能知道一条边的经过次数,那么就知道这条边是否在公共路径上

    容易想到树上差分,预处理一波 lca 后复杂度可行

    删边肯定贪心地删能删的最长边

    要如何判断删边后是否最长路径小于mid呢

    显然可以预处理出不删前的最长路径

    如果最长路径减去删的边 ≤ mid 那么其他路径减删去的边肯定不大于mid(注意删去的边在所有原长超过mid的路径上)

    至于其他原长小于 mid 的路径根本不用考虑

    所以总结一下就是二分+树上差分

    luogu第13个点真是用sang心xin良bing苦kuang,把复杂度卡满了

    求LCA可能用树剖会快一点,然而懒得写...

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    using namespace std;
    inline int read()
    {
        register int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    const int N=3e5+7;
    int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt;
    inline void add(int &a,int &b,int &c)
    {
        from[++cntt]=fir[a];
        fir[a]=cntt; to[cntt]=b; val[cntt]=c; 
    }
    
    int f[N][21],dep[N],dis[N],frm[N];//f和dep用来求LCA,dis是点到根的距离,用来求最长路径,frm是连接父节点的边的编号
    void dfs1(int &x,int &fa)//第一遍dfs处理f,dep,dis,frm
    {
        f[x][0]=fa; dep[x]=dep[fa]+1;
        for(int i=1;i<=19;i++) f[x][i]=f[f[x][i-1]][i-1];
        for(int i=fir[x];i;i=from[i])
        {
            int &v=to[i]; if(v==fa) continue;
            dis[v]=dis[x]+val[i]; frm[v]=i; dfs1(v,x);
        }
    }
    inline int LCA(int x,int y)//求LCA
    {
        if(dep[x]<dep[y]) swap(x,y);
        for(int i=19;i>=0;i--)
            if(dep[f[x][i]]>=dep[y]) x=f[x][i];
        if(x==y) return x;
        for(int i=19;i>=0;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    
    int n,m,cnt[N],lca[N],sum[N],tot,mxlen,rt=1;
    //cnt是差分数组,lca顾名思义,sum[i]是第i条路径的长度,tot记录有几条边原长大于mid,mxlen是最长路径长度,rt是根节点
    struct data
    {
        int a,b;
    }d[N];//存运输计划
    int pos;//记录最长边的编号
    void dfs2(int &x)//dfs2处理每条边经过次数
    {
        for(int i=fir[x];i;i=from[i])
        {
            int &v=to[i]; if(v==f[x][0]) continue;
            dfs2(v); cnt[x]+=cnt[v];
        }
        if(cnt[x]==tot&&val[frm[x]]>val[pos]) pos=frm[x];//如果当前节点被所有大于mid的边经过则考虑更新pos
    }
    inline bool pd(int &p)//判断合法性,p就是mid
    {
        tot=pos=0; memset(cnt,0,sizeof(cnt));//初始化
        for(register int i=1;i<=m;i++)
        {
            if(sum[i]<=p) continue;
            cnt[d[i].a]++; cnt[d[i].b]++;
            cnt[lca[i]]-=2;
            tot++;
            //记录差分
        }
        dfs2(rt);
        return mxlen-val[pos]>p ? 0 : 1;//判断
    }
    
    int main()
    {
        int a,b,c;
        n=read(); m=read();
        for(register int i=1;i<n;i++)
        {
            a=read(),b=read(),c=read();
            add(a,b,c); add(b,a,c);
        }
        dfs1(rt,rt);
        for(register int i=1;i<=m;i++)
        {
            d[i].a=read(); d[i].b=read();
            lca[i]=LCA(d[i].a,d[i].b);//预处理lca
            sum[i]=dis[d[i].a]+dis[d[i].b]-(dis[lca[i]]<<1);//求出sum
            mxlen=max(mxlen,sum[i]);//尝试更新maxlen
        }
        register int l=0,r=mxlen,mid;
        while(l<=r)
        {
            mid=l+r>>1;
            pd(mid) ? r=mid-1 : l=mid+1;
        }
        printf("%d",l);
        return 0;
    }
  • 相关阅读:
    剑指offer(链表)
    设计模式
    谷歌Colab使用(深度学习)
    Consul与python API注册与注销
    【日志收集】之Loki
    【消息队列】之 RabbitMQ安装
    【消息队列】之NSQ安装
    Docker基础
    Python3 , Mysql5.7 , Smb 安装
    SkyWalking部署
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9828248.html
Copyright © 2011-2022 走看看