• 【2017.12.02普及组模拟】送快递


    【2017.12.02普及组模拟】送快递

    描述

    题目描述

        Petya和Vasya被聘为快递员。在工作日期间,他们将提供包裹到线上的不同点。根据公司的内部规定,包裹的交付必须严格按照一定的顺序进行。最初,Petya处于坐标s1的点,Vasya位于坐标s2的点,n个顾客所需访问的顺序位于点x1,x2,...,xn。
        这些人预先同意他们谁将交付给哪些客户,然后他们的行为如下。当第i个客户端的包裹被交付时,两个快递员中的其中一个负责去送第i+1个。此时不送快递员的那个原地不动。即快递是严格按照顾客顺序一个个在送的,一个快递员在送的时候,另一个快递员是不动的。
         由于要相互沟通,这些家伙有对讲机。对讲机的工作距离不是很远,所以Petya和Vasya想在送快递的时候,使得他们的最大距离尽可能低。帮助Petya和Vasya尽量减少他们之间的最大距离,遵守所有交货规则。
    

    输入

      第一行包含三个整数n,s1,s2(1≤n≤100000,0≤s1,s2≤10^9) - Petya和Vasya的送货数量和起始位置。
      第二行包含n个整数x1,x2,...,xn - 客户坐标(0≤xi≤10^9),以便交货。
      保证,在数字s1,s2,x1,...,xn中没有两个相等。
    

    输出

    只有一行一个正数,即最小可能的最大距离。

    样例输入1

    2 0 10
    5 6

    样例输出1

    10

    样例输入2

    3 2 1
    3 4 5

    样例输出2

    1

    样例输入3

    1 4 5
    2

    样例输出3

    2

    注意

         在第一个测试案例中,快递员之间的初始距离为10.这个值将是答案,例如,Petya可以执行两次交付,Vasya将保持在起点。
        在第二个测试用例中,您可以通过以下方式进行最佳的操作:Vasya向第一个客户送货,Petya到第二个,最后,Vasya将包提供给第三个客户。按照这种交货顺序,快递员之间的距离不会超过1。
        在第三个测试用例中,只有两种情况是可能的:如果单个包装的交付由Petya执行,则它们之间的最大距离为5 - 2 = 3.如果Vasya将提供包装,最大距离为4 - 2 = 2.后一种方法是最优的。
    

    数据范围限制

    对于20%的数据,n<=25
    对于40%的数据, n<=100
    对于60%的数据, n<=2000
    对于100%的数据,n<=100000


    分析

    为了表示方便,设x1=s1x0=s2
    如果用暴力做,就会生成下图这样一棵搜索树:
    搜索树
    我们很容易发现,在第i层中,每个节点中必有i。
    将i提取出来,再将重复的节点(反正每个节点中两个数反过来都一样)压在一起,
    可以得到下面这张图:
    搜索树的简化
    从这张图中,很容易得出结论:在第i层中,若i-1层有节点,必定会生成i-1的节点。

    对于这一题,“最大距离尽可能低”,在一个句子中,一旦最大、最小两个词一起出现,就要果断二分(除非两个是并列关系),这是我目前没有见过例外的规律。
    所以,先二分答案,然后判断是否成立。

    判断是否成立时,我们考虑一层一层计算
    如果第i-1层有节点k,那么节点k就可以生成第i层的k节点和i-1节点
    这意味着,若k在某一层(假设为j)被删掉,那么在以后j+1到n层就不会生成k。
    还有之前已经得出的结论:在第i层中,若i-1层有节点,必定会生成i-1的节点。
    在第i层,节点k必须满足xk[xians,xi+ans],不然就要被删掉
    所以我们可以枚举行,在每一行删掉不满足这个条件的节点。若i-1行有节点,就试着加入i-1(若i-1不符合条件一样要删)。
    若到第n层有节点,则这个答案是成立的。

    为了更高效地删除节点,我使用了平衡树操作。
    时间复杂度O(lg109nlgn)

    代码

    set版

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <set>
    using namespace std;
    int n;
    int _s[100010];
    int* s=_s+1;
    set<int> q;
    inline bool ok(int);
    int main()
    {
        freopen("delivery.in","r",stdin);
        freopen("delivery.out","w",stdout);
        scanf("%d %d %d",&n,&s[-1],&s[0]);
        int i;
        for (i=1;i<=n;++i)
            scanf("%d",&s[i]);
        int l=abs(s[0]-s[-1]),r=1000000000,mid,res;
        while (l<=r)
        {
            mid=l+r>>1;
            if (ok(mid))
                r=(res=mid)-1;
            else
                l=mid+1;
        }
        printf("%d
    ",res);
        return 0;
    }
    inline bool ok(int len)
    {
        int i,up,low;
        set<int>::iterator p;
        q.clear();
        for (i=0;i<=n;++i)
        {
            up=s[i]+len;
            low=s[i]-len;
            //删掉小于low的节点
            while (!q.empty() && *(p=q.begin())<low)
                q.erase(p);
            //删掉大于up的节点
            while (!q.empty() && *(p=--q.end())>up)
                q.erase(p);
            //加入i-1。为什么不放在上面?首先,一定能生成i-1,不然在前面时早return 0了。其次,删除时,少一个节点当然会快一点。
            if (low<=s[i-1] && s[i-1]<=up)
                q.insert(s[i-1]);
            if (q.empty())
                return 0;
        }
        return 1;
    }

    splay版

    //注释同上
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    struct Node
    {
        int val,fa,l,r;
    } d[100001];
    int cnt,root;
    inline void zig(int x)
    {
        int y=d[x].fa;
        int z=d[y].fa;
        d[y].l=d[x].r;
        d[d[x].r].fa=y;
        d[x].fa=z;
        if (z)
        {
            if (d[z].l==y)
                d[z].l=x;
            else
                d[z].r=x;
        }
        d[x].r=y;
        d[y].fa=x;
    }
    inline void zag(int x)
    {
        int y=d[x].fa;
        int z=d[y].fa;
        d[y].r=d[x].l;
        d[d[x].l].fa=y;
        d[x].fa=z;
        if (z)
        {
            if (d[z].l==y)
                d[z].l=x;
            else
                d[z].r=x;
        }
        d[x].l=y;
        d[y].fa=x;
    }
    inline void splay(int x,int t)
    {
        int y,z;
        while (d[x].fa!=t)
        {
            y=d[x].fa;
            z=d[y].fa;
            if (z==t)
            {
                if (x==d[y].l)
                    zig(x);
                else
                    zag(x);
            }
            else if (d[z].l==y)
            {
                if (d[y].r==x)
                    zag(x);
                else
                    zig(y);
                zig(x);
            }
            else if (d[z].r==y)
            {
                if (d[y].l==x)
                    zig(x);
                else
                    zag(y);
                zag(x);
            }
        }
        if (!t)
            root=x;
    }
    inline void insert(int x,int val)
    {
        if (!root)
        { 
            cnt=1; 
            d[1]={val,0,0,0};
            root=1;
            return;
        }
        while (1)
        {
            if (val<d[x].val)
            {
                if (d[x].l)
                    x=d[x].l;
                else    
                {
                    d[++cnt]={val,x,0,0};
                    d[x].l=cnt;
                    splay(cnt,0);
                    break;
                }
            }
            else if (val>d[x].val)
            {
                if (d[x].r)
                    x=d[x].r;
                else
                {
                    d[++cnt]={val,x,0,0};
                    d[x].r=cnt;
                    splay(cnt,0);
                    break;
                }
            }
        }
    }
    inline int pred(int val)
    {
        int x=root,ret=0;
        while (x)
        {
            if (d[x].val<val)
            {
                ret=x;
                x=d[x].r;
            }
            else
                x=d[x].l;
        }
        return ret;
    } 
    inline int succ(int val)
    {
        int x=root,ret=0;
        while (x)
        {
            if (d[x].val>val)
            {
                ret=x;
                x=d[x].l;
            }
            else
                x=d[x].r;
        }
        return ret;
    }
    int n;
    int _s[100010];
    int* s=_s+1;
    inline bool ok(int);
    int main()
    {
        freopen("delivery.in","r",stdin);
        freopen("delivery.out","w",stdout);
        scanf("%d %d %d",&n,&s[-1],&s[0]);
        int i;
        for (i=1;i<=n;++i)
            scanf("%d",&s[i]);
        int l=abs(s[0]-s[-1]),r=1000000000,mid,res;
        while (l<=r)
        {
            mid=l+r>>1;
            if (ok(mid))
                r=(res=mid)-1;
            else
                l=mid+1;
        }
        printf("%d
    ",res);
        return 0;
    }
    inline bool ok(int len)
    {
        int i,up,low,x;
        cnt=root=0;
        for (i=0;i<=n;++i)
        {
            up=s[i]+len;
            low=s[i]-len;
            if (x=pred(low))
            {
                splay(x,0);
                root=d[x].r;
                d[root].fa=0;
            }
            if (x=succ(up))
            {
                splay(x,0);
                root=d[x].l;
                d[root].fa=0;
            }
            if (low<=s[i-1] && s[i-1]<=up)
                insert(root,s[i-1]);
            if (root==0)
                return 0;
        }
        return 1;
    }                        

    注意

    1. 不会用set的,上百度。不会用splay的,上百度。
    2. set用红黑树实现,在这题中,splay的删除相对高效。
      删掉小于low的节点时,只需把最大的小于low的节点旋转到根,留下根的右子树。
      删掉大于up的节点时道理一样
    3. set操作很简单但跑得慢,splay要手打但跑得快。
    4. 有某位大佬发明了枚举列的算法,完美利用数据的随机性,能过!!!主要是有了一些break。但最坏情况下时间复杂度O(lg109n2)。在随机数据下,时间远远没有这么多。 ZHJ的代码,判断是否成立时明显两重循环,却跑了360ms。
  • 相关阅读:
    最小的K个数
    堆排序
    归并排序
    希尔排序
    快速排序
    二分查找
    数组中出现次数超过一半的数字
    包含min函数的栈
    栈的压入、弹出序列
    中缀表达式转后缀表达式
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145298.html
Copyright © 2020-2023  润新知