搭积木(block)
【问题描述】
小 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 }