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;
    }

     

  • 相关阅读:
    1、编写一个简单的C++程序
    96. Unique Binary Search Trees
    python 操作redis
    json.loads的一个很有意思的现象
    No changes detected
    leetcode 127 wordladder
    django uwsgi websocket踩坑
    you need to build uWSGI with SSL support to use the websocket handshake api function !!!
    pyinstaller 出现str error
    数据库的读现象
  • 原文地址:https://www.cnblogs.com/ivanovcraft/p/9066441.html
Copyright © 2011-2022 走看看