【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\n",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\n",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。
posted @ 2017-12-04 20:56  jz_597  阅读(169)  评论(0编辑  收藏  举报