[题解] 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\) 比较即可。

实现细节
  1. \(cnt\) 的计算会爆 long long,需要开 __int128。
  2. 整数三分因为精度不够,到区间长度很小的时候容易死循环,解决方法是当区间长度很小的时候就停止三分,改用暴力求剩下的部分。
  3. 三分中,如果 \(f(lmid)=f(rmid)\),可以同时收缩左右两端点,这在本题中可以让代码快非常多。
  4. 算好二分三分上下界也可以让代码变快。
点击查看代码
#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;
}
posted @ 2026-07-07 16:22  Cute_lxy  阅读(5)  评论(0)    收藏  举报