牛客网暑期ACM多校训练营(第二场) G transform
题意: 现有n个集装箱摆在一排,每个集装箱有自己的位置x[i]以及现在装有的物品的数量a[i]。现在要在一个位置将这些东西售卖出去,于是需要将产品运送到某一个位置,已知从位置u到位置v运送一个物品需要的体力值是2*abs(u-v);问有体力值消耗不超过T的情况下最多可以售卖多少物品。
做法:首先我们可以知道,因为我们要将所有的物品运送到一个位置,于是运送的这些集装箱的位置一定是一段区间,不会是断开的,为什么呢,你想啊,假设Y代表该集装箱被运了,X代表该集装箱没有被运,M代表终点码头,如果是断开的,例如YYXMYYY,我们将最端点的集装箱不运或者少运几个,取决于X码头的产品数量,无论是哪一种,改为X码头晕倒M码头是不是都比原来的省体力呢。
于是我们已经知道是一段连续的区间了,我们就二分最后售卖的物品的数量,对于每次二分的结果,我们验证的时候就O(n)的跑一遍,以每个结点为左端点,运送mid个物品,体力消耗合不合法即可。需要枚举两遍,分别为左右端点。
具体的一些细节看代码,比如说如何计算体力值什么的。
这里官方题解给的时间复杂度是O(n*log(sum(a[i]))),自我感觉因为每个端点作为左端点的时候我们还要向右跑,具体跑多远取决于数据,估计比这个复杂度高,但是有跳出。
代码如下:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const long long maxn = 500010; long long n; ///n个码头 long long T; ///允许的最大的体力值 long long x[maxn], a[maxn]; ///分别代表每个集装箱的位置与现有的容量 long long sum_num[maxn]; ///记录集装箱容量的前缀和 long long sum_T[maxn]; ///记录前i个集装箱的物品全部运到0号节点需要的体力值 这样记录可以O(1)的算出来区间转移的体力值 可以作为一个窍门 void init() { sum_num[0] = 0; sum_T[0] = 0; } void input() { for(long long i=1; i<=n; i++) { scanf("%lld", &x[i]); } for(long long i=1; i<=n; i++) { scanf("%lld", &a[i]); sum_num[i] = sum_num[i-1]+a[i]; sum_T[i] = sum_T[i-1]+2*a[i]*x[i]; ///将该物品也运到0号点 // printf("%lld..%lld...\n" , sum_num[i] , sum_T[i]); } } long long LL(long long l , long long r) { return (sum_T[r]-sum_T[l-1])-(sum_num[r]-sum_num[l-1])*x[l]*2; ///先将这个区间内的东西移动到0点 再减去多移动的x[l]的距离 } long long RR(long long l , long long r) { return (sum_num[r]-sum_num[l-1])*x[r]*2-(sum_T[r]-sum_T[l-1]); } bool ok(long long ans) { long long l , r , mid , mid_num; ///这里的l,r,mid不是二分 是枚举左右端点和中点 我们知道一定是把这个区间的货物运送到中点 ///这里的中点指的不是区间位置的中点,指的是货物总量中点所在的位置 mid_num = ans/2+1; ///首先我们来看,左端点一定被全部运送,右端点可以不全部运送 l = r = mid = 1; while( true ) { while(r<=n && (sum_num[r]-sum_num[l-1])<ans) r++; ///找到右端点 右端点不一定全部都运 while(mid<r && (sum_num[mid]-sum_num[l-1])<mid_num) mid++; ///找到货物中点所在的位置 if(r>n || mid>n) break; ///枚举到最后的左端点就跳出 long long tmp = (sum_num[r]-sum_num[l-1])-ans; ///确定右端点集装箱剩余多少个物品 // printf("%lld...%lld..%lld..%lld..%lld...%lld...%lld...++++\n" , ans , l , r , mid ,RR(l , mid) , LL(mid , r) , 2*tmp*(x[r]-x[mid])); if((RR(l , mid)+LL(mid , r)-2*tmp*(x[r]-x[mid]))<=T) return true; ///RR代表将位置i,j区间内的物品全部移到j位置需要的体力 ///LL代表将位置i,j区间内的物品全部移到i位置需要的体力 l++; } ///接下来我们看右端点一定被全部运送,左端点不一定的情况,同上 l = r = mid = n; while( true ) { while(l>=1 && (sum_num[r]-sum_num[l-1])<ans) l--; while(mid>l && (sum_num[r]-sum_num[mid-1])<mid_num) mid--; if( l<1 || mid<l) break; long long tmp = (sum_num[r]-sum_num[l-1])-ans; // printf("%lld...%lld..%lld..%lld..%lld...%lld...%lld...----\n" , ans , l , r , mid , RR(l , mid) , LL(mid , r) , 2*tmp*(x[mid]-x[l])); if((RR(l , mid)+LL(mid , r)-2*tmp*(x[mid]-x[l]))<=T) return true; r--; } return false; } void solve() { long long l , r , mid , res; l = 1; r = sum_num[n]; ///二分枚举最后卖出的货物的数量 下限是1 上线时全部 while( l<=r ) { mid = (l+r)/2; // printf("%lld..\n" , mid); if( ok(mid) ) { // printf("%lld..\n" , mid); res = mid; l = mid+1; } else { r = mid-1; } } printf("%lld\n" , res); } int main() { while( scanf("%lld%lld", &n, &T) != EOF ) { init(); input(); // printf("%lld...\n" , LL(1 , 2)); solve(); } ///别忘了开long long return 0; }