题目链接:https://uoj.ac/contest/5/problem/49
做法
CNM,血压高了,一直卡在官方题解中的算法 2 ,根本就没有想到是二分答案,悲。
首先,一个十分显然的事情,这题有两种不同的二分方法:
- 如果指定左右两边最远的到达距离,你可以在 $O(n)$ 的时间内对所有位置处理出其对应的区间,但是由于二分最远到达距离只能作用在一个数上,所以时间复杂度是: $O(n\log{n}\log_{X})$ ,也就是算法二。
- 既然答案要求箱子数,就二分箱子数 $K$ ,同理,可以在 $O(n)$ 的时间处理出对应的区间,然后算一下最小代价就行了,时间复杂度:$O(n\log{X})$ ,这就是题解的做法。
为什么啊,纯二分题没有做出来,你真的是越来越菜了QAQ。
处理细节:考虑处理出包含 $x$ 的最长的区间,满足两个要求:
- 区间内的箱子数和 $<K$。
- 区间为这个位置搬箱子最优方案的一种。
什么叫搬箱子的最优方案,显然:优先搬最近的。
所以每次把最近的不在区间中的位置加入区间所得到的任意一个方案都是最优方案。
可以证明,这个区间的左右端点会随着位置右移而非严格递增。
然后直接写就行了。
时间复杂度:$O(n\log{X})$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include<cstdio> #include<cstring> #include<algorithm> #define N 510000 using namespace std; typedef long long LL; LL a[N],fa[N],ff[N],b[N],sum,T; LL limit; int n,left,right; LL dis(int x,int y){return b[y]-b[x];} void solve1(int i){while(dis(left,i)>dis(i,right) && sum>limit)sum-=a[left++];} bool check(){ sum=0;left=1;right=0; LL mincost=T+1; for(int i=1;i<=n;i++){ if(right<i)right++,sum+=a[right]; solve1(i); while(right<n && (left==1 || dis(left-1,i)>dis(i,right+1)) && sum<=limit){ right++,sum+=a[right]; solve1(i); } if(sum>limit)sum-=a[right--]; LL now=ff[right]-ff[i]-b[i]*(fa[right]-fa[i])+b[i]*(fa[i-1]-fa[left-1])-(ff[i-1]-ff[left-1]); LL fnow=T+1; if(left>1)fnow=min(dis(left-1,i)*(limit-sum),fnow); if(right<n)fnow=min(dis(i,right+1)*(limit-sum),fnow); if(left==1 && right==n)fnow=0; mincost=min(mincost,now+fnow);
} return mincost*2<=T; } int main(){ scanf("%d%lld",&n,&T); LL l=0,r=0,ans; for(int i=1;i<=n;i++)scanf("%lld",&b[i]); for(int i=1;i<=n;i++){scanf("%lld",&a[i]);l=max(a[i],l);r+=a[i];fa[i]=fa[i-1]+a[i];} for(int i=1;i<=n;i++)ff[i]=ff[i-1]+b[i]*a[i];
while(l<=r){ limit=(l+r)>>1; if(check())ans=limit,l=limit+1; else r=limit-1; } printf("%lld\n",ans); return 0; }
|