题意:数轴上某些点有若干物品,任选起点在规定时间T内移动到附近将一个物品取回起点。移动单位距离的时间代价为1,取物品不需付出时间,求最多保留的物品
算法:二分答案+贪心判断
知道做法之后思路是很清晰的,难的是想出正解还有实现时处理好各种细节
二分答案之后将求值转化为判断
几个问题:
1.如何判断:在时间T内,能否取到x的值(物品)
易得取的必是连续的一段区间
从左到右枚举起点,易知起点离左边越来越远,离右边越来越近,则该区间一定是单调不下降的
所以每次移动起点,就贪心地比较左右边界(到起点的距离)。
若右边更优,则舍弃左边;直到左边界比右边界更优,此时如果再去移动区间的话,就变成了用左边的优值去交换右边的差值,继续向右拓展只会取到更劣的解。
这样对于每个起点(x=p),就能O(p)地找到最优的区间
2.记录区间的起点、左右边界之后,如何O(1)地求出付出的时间代价
需要简单的数学技巧。
读入时维护a数组的前缀和cnt,以及a*x的前缀和sum
sum数组是解决问题的关键,其数学意义即为在求从起点往位置x[i]移动时,需要付出的代价即为(取的个数*距离)
经过数学变换可推出
cost=(cnt[st]-cnt[l])*x[st]-(sum[st]-sum[l])+l_num*(x[st]-x[l])+r_num*(x[r]-x[st])-(cnt[r-1]-cnt[st])*x[st]+(sum[r-1]-sum[st]);
1 cost=(cnt[st]-cnt[l])*x[st]-(sum[st]-sum[l])+l_num*(x[st]-x[l])+ //Left 2 r_num*(x[r]-x[st])-(cnt[r-1]-cnt[st])*x[st]+(sum[r-1]-sum[st]);//Right 3 对于左半区间:用区间内权值的和*起点坐标,利用sum数组减去多余的值; 4 左端点的代价可以直接求出 5 右半区间同理。
最后由于往返,代价*2
1 #include<cstdio> 2 #include<cstring> 3 #include<cctype> 4 #include<algorithm> 5 using namespace std; 6 #define maxn 500010 7 #define ll long long 8 ll n,t; 9 ll cnt[maxn],sum[maxn],x[maxn],a[maxn]; 10 ll read(){ 11 ll x=0;char ch=getchar(); 12 while (!isdigit(ch)){ch=getchar();} 13 while (isdigit(ch)){x=x*10+ch-'0';ch=getchar();} 14 return x; 15 } 16 void init(){ 17 n=read(),t=read(); 18 for (int i=1;i<=n;i++) x[i]=read(); 19 for (int i=1;i<=n;i++) a[i]=read(); 20 for (int i=1;i<=n;i++) cnt[i]=cnt[i-1]+a[i],sum[i]=sum[i-1]+a[i]*x[i]; 21 } 22 bool ok(int goal){ 23 ll l=1,r=lower_bound(cnt+1,cnt+n+1,goal)-cnt,l_num=a[1],r_num=goal-cnt[r-1],d1,d2,now,cost; 24 for (int st=1;st<=n;st++){ 25 while (l<st&&r<=n){ 26 int d1=x[st]-x[l],d2=x[r]-x[st]; 27 if (d1>d2){ 28 now=min(l_num,a[r]-r_num); 29 l_num-=now,r_num+=now; 30 if (l_num==0) l++,l_num=a[l]; 31 if (r_num==a[r]) r++,r_num=0; 32 }else break; 33 } 34 cost=(cnt[st]-cnt[l])*x[st]-(sum[st]-sum[l])+l_num*(x[st]-x[l]) 35 +r_num*(x[r]-x[st])-(cnt[r-1]-cnt[st])*x[st]+(sum[r-1]-sum[st]); 36 cost<<=1;if (cost<=t) return 1; 37 } 38 return 0; 39 } 40 int main(){ 41 init(); 42 ll left=0,right=cnt[n]+1,mid=(left+right)>>2; 43 while (left<right){ 44 mid=(left+right)>>1; 45 if (ok(mid)) left=mid+1; 46 else right=mid; 47 } 48 printf("%lld ",left-1); 49 return 0; 50 }