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

    拿到题目的第一眼


    首先这是一棵n个节点的树(别说你看不出来)


    然后对于树上的m条链
    我们可以选取树上的唯一一条边使它的边权变为0


    求处理后最长链的长度


    20分


    m=1
    好啦,好像可做
    一眼望去全是水


    只需求出一条链上的所有边并计算边权和及最大边权(暴力往上跳并记录即可)
    边权和减去最大边权即为答案
    那么我们就可以O(n)过掉这道题了(不嫌麻烦的话也可以O(log n)搞树上路径)


    60分?


    从未如此接近满分
    全是链,这意味着什么(并不意味着什么)


    想了想,发现好像60分并不好搞
    考虑一下暴力吧


    超级暴力:暴力枚举删每一条边,统计删完这条边之后最长链的长度,取最小值就是答案,复杂度O(n^2 m),25分


    刚才的小优化


    考虑优化暴力


    枚举删哪条边O(n)显然已经达到理论下限
    如果非要搞它的话只能排除那些不被经过的边,效率高不了多少


    接下来是统计每条链的长度
    全是链哎,求线性区间和,前缀和优化,消去一个O(n)


    那个O(m)好像没有什么有效的优化


    这样,复杂度降至O(nm),40分


    然后其他数据,搞树链剖分动态修改、查询可以多拿一些分,复杂度O(nm log n),60分


    怎么办


    QAQ,60分都拿不到了吗


    可不可以不实际改边权呢?


    经过不会就猜二分
    经过深入思考,我们发现:


    最短时间为t,前提是对于length>t的所有链,总能找到至少一条长为k公共边,使得最长链的长度max length-k<=t
    如果知道答案,好像不仅不用枚举最长链,还可以把枚举删边变为贪心删掉被全部满足条件的链经过的最长边,稳赚一个O(n)和一个O(m)


    考虑二分答案


    如果能够在时间t1内完成任务,那么对于t2>t1,总能在时间t2内完成任务
    所以答案符合单调性
    可以二分答案


    Check函数怎么写呢,看一看能不能找到找到至少一条长为k公共边,使得最长链的长度max length-k<=t
    设length>t的边的个数为number
    我们必须知道一条边是否曾被number个链同时经过,唯一的方法好像就是差分了,check函数可以写成O(n + m)的,总复杂度O((n + m)log n),60分


    100分


    二分答案的做法放到树上呢


    考虑线性数据上二分的完整做法
    预处理每一条链的length,二分答案,放到check函数里搞


    没问题
    LCA求出每条链的length,还是二分,check函数换成树上差分
    最后发现正解只要一句话:
    求链长+二分


    上代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn=3e5+10;
    struct edge{
        int next,to,dis;
    }e[2*maxn],q[2*maxn];
    struct length{
        int len,lca,u,v;
    }len[maxn];
    int head[maxn],cnt,headq[maxn],dis[maxn],maxlen,n,m,a[maxn],ans,s[maxn],num,ret,f[maxn];    //一堆变量
    bool vis[maxn];
    inline int readn()        //随处可见的快读
    {
        int x=0;
        char ch=getchar();
        while(ch>'9'||ch<'0')
            ch=getchar();
        while(ch>='0'&&ch<='9')
        {
            x=(x<<3)+(x<<1)+(ch^'0');
            ch=getchar();
        }
        return x;
    }
    inline void add_edge(int x,int y,int d)
    {
        e[++cnt].next=head[x];
        e[cnt].to=y;
        e[cnt].dis=d;
        head[x]=cnt;
    }
    inline void add_que(int x,int y)
    {
        q[++cnt].next=headq[x];
        q[cnt].to=y;
        headq[x]=cnt;
    }
    int find(int x)
    {
        return f[x]==x?f[x]:f[x]=find(f[x]);    //一行并查集
    }
    void dfs(int u,int pre)
    {
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==pre)
                continue;
            dfs(v,u);
            s[u]+=s[v];        //统计经过次数
        }
        if(s[u]==num&&a[u]>ret)
            ret=a[u];        //贪心地选取最长公共边
    }
    inline bool check(int x)
    {
        memset(s,0,sizeof(s));
        num=ret=0;
        for(int i=1;i<=m;i++)        //树上差分
            if(len[i].len>x)
            {
                s[len[i].u]++;
                s[len[i].v]++;
                s[len[i].lca]-=2;
                num++;        //记录len>x的链的个数
            }
        dfs(1,0);        //跑差分结果
        if(maxlen-ret>x)    //如果不能满足,返回NO
            return 0;
        return 1;    //能满足
    }
    void tarjan(int u,int pre)        //tarjan求链长
    {
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==pre)
                continue;
            dis[v]=dis[u]+e[i].dis;
            tarjan(v,u);
            a[v]=e[i].dis;
            int f1=find(v);
            int f2=find(u);
            if(f1!=f2)
                f[f1]=find(f2);
            vis[v]=1;
        }
        for(int i=headq[u];i;i=q[i].next)
            if(vis[q[i].to])
            {
                int p=(i+1)>>1;
                len[p].lca=find(q[i].to);
                len[p].len=dis[u]+dis[q[i].to]-2*dis[len[p].lca];
                maxlen=max(maxlen,len[p].len);
            }
    }
    int main()
    {
        n=readn(),m=readn();
        for(int i=1;i<n;i++)
        {
            int ai=readn(),bi=readn(),ti=readn();
            add_edge(ai,bi,ti);        //邻接表存图
            add_edge(bi,ai,ti);
        }
        for(int i=1;i<=n;i++)
            f[i]=i;
        cnt=0;
        for(int i=1;i<=m;i++)
        {
            int x=readn(),y=readn();        //输入
            len[i].u=x;
            len[i].v=y;
            add_que(x,y);
            add_que(y,x);
        }
        tarjan(1,0);
        int l=0,r=maxlen,mid;
        while(l<=r)        //二分答案
        {
            mid=(l+r)>>1;
            if(check(mid))
            {
                r=mid-1;
                ans=mid;    //记录答案
            }
            else
                l=mid+1;    //不能满足则二分更大答案,以使得条件可以得到满足
        }
        printf("%d
    ",ans);
        return 0;
    }

     

  • 相关阅读:
    Xamarin.Forms学习之位图(一)
    Xamarin.Forms学习之Platform-specific API和文件操作
    Xamarin.Forms学习之Page Navigation(二)
    Xamarin.Forms学习之Page Navigation(一)
    Xamarin.Forms学习之XAML命名空间
    Xamarin.Forms学习之初
    新手浅谈C#Task异步编程
    新手浅谈Task异步编程和Thread多线程编程
    新手浅谈C#关于abstract和interface
    Boost C++ 库
  • 原文地址:https://www.cnblogs.com/ivanovcraft/p/9066441.html
Copyright © 2011-2022 走看看