【问题描述】
小 OY 是一个喜欢搭积木的孩子,他有一天决定向小 C 展示他特别的搭积木
技巧。
现在一条直线上从左到右有 n 个位置,标号 1..n,第 i 个位置坐标为 x_i。
每个位置上都预先叠好了一些积木,其中第 i 个位置上叠了 a_i 块积木。
小 OY 一开始会向小 C 指定 1..n 中的某个位置 s,然后,他在第 0 秒从位置
s 出发,开始搭积木。
他可以做这些动作:
1、向左移动 1 个单位坐标,用时 1 秒。
2、向右移动 1 个单位坐标,用时 1 秒。
3、从当前位置顶部拿起一块积木,瞬间完成。
4、把拿着的积木叠到当前位置,瞬间完成。
由于小 OY 很小,任意时刻他手上至多只能带一块积木。
当时间到达第 T 秒时,小 OY 不会再进行移动。这时候,如果位置 s 的积木
叠得越高,就显得小 OY 叠积木本领越强。
现在,小 OY 在思考,如果他的移动策略足够高明,并且位置 s 也选定得恰
到好处,那么第 T 秒时位置 s 最多能叠到多少块积木呢?
【输入】
输入文件名:block.in
第一行两个数 n、T。
第二行 n 个严格递增的整数,第 i 个数为 x_i。
第三行 n 个非负整数,第 i 个数为 a_i。
【输出】
输出文件名:block.out
第一行一个数,为从最优的 s 出发在最高明的移动策略下,第 T 秒位置 s 至
多能叠到多少块积木。
【数据范围】
测试点 1..3:n≤100,T≤1000
测试点 4..5:a_i≤1
测试点 6..7:x_i = i
测试点 1..8:n≤10 5
测 试点 1..10: 1≤n≤ 5*10 5 ,0≤ T≤ 10 18 ,0≤ a_i≤10 4 ,0 ≤ x_i≤10 9

算法 1
先枚举 s,然后从近到远把其他位置的积木一个个拿到 s 来,直到时间 T 耗尽
为止。复杂度 O(nT)。
算法 2
优化一下算法 1,假设当前取到的最左、最右位置分别为 l、r,然后每次一下
取 min(a[l],a[r])个,如果取完后时间不超过 T 就取完,否则直接计算还能取几
个。由于每次左右指针都至少有一个会往边界移,复杂度 O(n^2)。
算法 3
先二分答案 k,问题成了:求把位置 s 叠到高度 k 所需的最短时间。
从左到右枚举 s,那么肯定是从前 k 近的地方搬来积木。我们想象每个位置 i
的 a[i]个积木是从左到右紧密排列在坐标 x[i]的,令 l 表示前 k 近的积木最左的
那块,r 表示最右的那块,随着 s 的右移,左边的积木们越来越远,右边的越来越
近,那么 l、r 是非降的。
暴力移动指针的话,复杂度是 O(Σ a[i])的。

正解:

在算法3的基础上如果时间不超过就尽可能取完,因为l,r非降,所以可以用

类似单调队列的思想,移动左右

lc表示a[l]未取的积木,rc表示a[r]已取的积木

先得出把1堆到k的最短时间,得到r和rc

之后考虑把s位置右移,右移后在不考虑l,r的移动的情况下

等价于把1~i的积木右移,i+1~r的积木向右撤回

移动完更新时间后,如果l比r距离s远,那么不如把l积木不取,来取r的积木

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 typedef long long ll;
 8 int n;
 9 ll a[500001],x[500001];
10 ll s[500001],T,ans;
11 ll sum(int l,int lc,int r,int rc)
12 {
13   if (l==r)
14     return rc-lc;
15   return (s[r-1]-s[l]+a[l]-lc+rc);
16 }
17 bool check(ll need)
18 {int i;
19   int l=1,r=n+1;
20   ll lc=0,rc=0;
21   ll s=0;
22   ll cur=0;
23   for (i=1;i<=n;i++)
24     {
25       if (s+a[i]<=need)
26     {s+=a[i];cur+=(x[i]-x[1])*a[i];}
27       else
28     {
29       rc=need-s;r=i;cur+=(x[i]-x[1])*rc;
30       break;
31     }
32     }
33   if (cur<=T) return 1;
34   for (i=2;i<=n;i++)
35     {
36       cur+=(x[i]-x[i-1])*(sum(l,lc,i,0)-sum(i,0,r,rc));
37       while (r<=n&&(x[i]-x[l])>(x[r]-x[i]))
38     {
39       int p=min(a[l]-lc,a[r]-rc);
40       cur+=(x[r]-x[i]-x[i]+x[l])*p;
41       lc+=p;rc+=p;
42       if (lc>=a[l]) l++,lc=0;
43       if (rc>=a[r]) r++,rc=0;
44     }
45       if (cur<=T) return 1; 
46     }
47   return 0;
48 }
49 int main()
50 {int i;
51   cin>>n>>T;
52   T/=2;
53   for (i=1;i<=n;i++)
54     {
55       scanf("%lld",&x[i]);
56     }
57   for (i=1;i<=n;i++)
58     {
59       scanf("%lld",&a[i]);
60       s[i]=s[i-1]+a[i];
61     }
62   ll l=0,r=s[n];
63   while (l<=r)
64     {
65       ll mid=(l+r)/2;
66       if (check(mid)) ans=mid,l=mid+1;
67       else r=mid-1;
68     }
69   cout<<ans;
70 }