【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
分析
为了表示方便,设
如果用暴力做,就会生成下图这样一棵搜索树:
我们很容易发现,在第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必须满足
所以我们可以枚举行,在每一行删掉不满足这个条件的节点。若i-1行有节点,就试着加入i-1(若i-1不符合条件一样要删)。
若到第n层有节点,则这个答案是成立的。
为了更高效地删除节点,我使用了平衡树操作。
时间复杂度
代码
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;
}
注意
- 不会用set的,上百度。不会用splay的,上百度。
- set用红黑树实现,在这题中,splay的删除相对高效。
删掉小于low的节点时,只需把最大的小于low的节点旋转到根,留下根的右子树。
删掉大于up的节点时道理一样 - set操作很简单但跑得慢,splay要手打但跑得快。
- 有某位大佬发明了枚举列的算法,完美利用数据的随机性,能过!!!主要是有了一些break。但最坏情况下时间复杂度
O(lg109n2) 。在随机数据下,时间远远没有这么多。 ZHJ的代码,判断是否成立时明显两重循环,却跑了360ms。