[题解] P15260 [USACO26JAN2] Balancing the Barns G
P15260 [USACO26JAN2] Balancing the Barns G
一道非常好的二分题。
首先不难发现,答案 \(\max\{a_i\}-\min\{b_i\}\) 与操作次数 \(cnt\) 之间有单调性,所以考虑二分找最小的不平衡度使得 \(cnt \le k\)。
现在问题来了,怎么求出操作次数呢?
观察数据范围,\(n\) 是 \(5 \times 10^4\),不难推断出总复杂度大概是 \(O(n \log^2 V)\) 左右,所以我们考虑用一个 \(O(n \log V)\) 复杂度的算法求解,设当前二分的值为 \(x\)。
不平衡度与 \(a_i\) 的最大值和 \(b_i\) 的最小值有关,想要解决问题我们肯定不能抛开这两个东西不管。
可以想到先固定下来一个减少未知量的个数,设 \(y=\max\{a_i\}\)。因为 \(a,b\) 之间的差值至多为 \(mid\),所以 \(min\{b_i\}\) 最少也得达到 \(y-x\)。
于是对于 \(i\) 这个位置,我们要进行至少 \(\max\{0,a_i-y,y-x-b_i\}\) 次操作,那总操作次数求个和就行了,也就是 \(cnt=\sum\max\{0,a_i-y,y-x-b_i\}\)。
这是一个 \(cnt\) 关于 \(y\) 的函数,\(\max\) 里的东西就是三条直线,斜率分别是 \(-1,0,1\),在坐标系中画一堆这样的直线不难发现,取靠上的部分后一定可以得到一个“V”或“凹”型的图像。
所以这个函数是单峰的,并且是严格单峰的,所以直接上三分求出极值点,然后和 \(k\) 比较即可。
实现细节
- \(cnt\) 的计算会爆 long long,需要开 __int128。
- 整数三分因为精度不够,到区间长度很小的时候容易死循环,解决方法是当区间长度很小的时候就停止三分,改用暴力求剩下的部分。
- 三分中,如果 \(f(lmid)=f(rmid)\),可以同时收缩左右两端点,这在本题中可以让代码快非常多。
- 算好二分三分上下界也可以让代码变快。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10, INF=1e18;
int a[N], b[N];
int n, k, maxx, minn;
inline __int128 f(int x, int y){
__int128 cnt=0;
for(int i=1; i<=n; i++)
cnt+=max({0ll, a[i]-y, y-x-b[i]});
return cnt;
}
inline bool pd(int x){
int l=minn+x, r=maxx;
while(l+3<r){
int mid=(l+r)>>1;
int lmid=mid-1, rmid=mid+1;
__int128 i=f(x,lmid),j=f(x,rmid);
if(i>j) l=lmid;
else if(j>i) r=rmid;
else l=lmid,r=rmid;
}
int ans=l;
for(int i=l+1; i<=r; i++)
if(f(x,i)<f(x,ans)) ans=i;
__int128 cnt=f(x,ans);
return cnt<=k;
}
inline void solve(){
cin>>n>>k;
maxx=-INF,minn=INF;
for(int i=1; i<=n; i++) cin>>a[i], maxx=max(maxx, a[i]);
for(int i=1; i<=n; i++) cin>>b[i], minn=min(minn, b[i]);
int l=-INF, r=maxx-minn;
while(l<r){
int mid=(l+r)>>1;
if(pd(mid)) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int t; cin>>t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号