二分法练习题

寻找段落(洛谷P1419)


题目大意

给定长度为n的序列a,求给定长度范围的连续子序列的最大平均值。

解题思路

根据题目条件可知平均值范围在\([-10^4, 10^4]\)之间,对平均值进行二分,二分判断时,先将序列a全部减去mid形成行序列b,此时只需判断序列b是否存在长度为[S,T]范围内的子序列之和为非负数,存在则说明仍存在更大的平均值。求得序列b的前缀和再用单调队列进行判断。

未知的代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,s,t;
    cin>>n>>s>>t;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)cin>>a[i];
    auto check=[&](double x){
        vector<double>b(n+1,0),sum(n+1,0);
        for(int i=1;i<=n;i++){
            b[i]=(double)(a[i]-x);
            sum[i]=sum[i-1]+b[i];
        }
        deque<int>q;
        for(int i=1;i<=n;i++){
            if(i>=s){
                while(!q.empty()&&sum[q.back()]>sum[i-s])q.pop_back();
                q.push_back(i-s);
            }
            if(!q.empty()&&q.front()<i-t)q.pop_front();
            if(!q.empty()&&sum[i]-sum[q.front()]>=0)return true;
        }
        return false;
    };
    double l=-1e4,r=1e4;
    for(int i=0;i<100;i++){
        double mid=(l+r)/2;
        if(check(mid))l=mid;
        else r=mid;
    }
    cout<<fixed<<setprecision(3)<<l<<endl;
    return 0;
}

借教室(洛谷 P1083)


题目大意

对接下来n天,每天有\(r_i\)个教室可供租借,现有m个订单,每个订单包含三个参数\(d_j,s_j,t_j\),表示从\(s_j\)天到\(t_j\)天要使用\(d_j\)个空闲教室,现要判断是否有订单无法满足(订单按顺序执行),如果有需通知哪个申请人修改订单。

解题思路

采用前缀和判断空闲教室数是否满足,若不满足则对订单编号进行二分。

未知的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    int n,m;
    cin>>n>>m;
    vector<ll>r(n+5);
    for(int i=1;i<=n;i++)cin>>r[i];
    vector<array<ll,3>>ord(m+5);
    for(int i=1;i<=m;i++)cin>>ord[i][0]>>ord[i][1]>>ord[i][2];
    auto check=[&](ll x){
        vector<ll>f(n+5,0);
        for(int i=1;i<=x;i++){
            f[ord[i][1]]+=ord[i][0];
            f[ord[i][2]+1]-=ord[i][0];
        }
        ll sum=0;
        for(int i=1;i<=n;i++){
            sum+=f[i];
            if(sum>r[i])return true;
        }
        return false;
    };
    if(!check(m))cout<<0<<endl;
    else{
        int l=1,r=m;
        while(l<r){
            int mid=(l+r)>>1;
            if(check(mid))r=mid;
            else l=mid+1;
        }
        cout<<-1<<endl<<l<<endl;
    }
    return 0;
}

跳石头(洛谷P2678)


题目大意

为提高跳石头比赛的难度,现移走不超过M个石头,求最短跳跃距离的最大值。

解题思路

二分距离,l=0,r=L,计算每两相邻石头的距离(考虑移走石头后相邻石头的下标变化),若小于mid则要移走,最后判断计数是否不超过M。

未知的代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    int L,n,m;
    cin>>L>>n>>m;
    vector<int>d(n+2,0);
    for(int i=1;i<=n;i++)cin>>d[i];
    d[n+1]=L;
    auto check=[&](int x){
      int cnt=0,p=0;
      for(int i=1;i<=n+1;i++){
          if(d[i]-d[p]<x)cnt++;
          else p=i;
      }
      return cnt<=m;
    };
    int l=0,r=L,ans;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }else r=mid-1;
    }
    cout<<ans<<endl;
    return 0;
}

聪明的质监员(洛谷P1314)


题目大意

选出参数w使得检验结果与标准值之差最小。

解题思路

对w进行二分,根据数据确定w范围,本题所求最小值需要记录维护,根据所给公式,使用前缀和记录重量w达标的个数和及其价值和,最后求得y的和并与ans比较更新最小值,二分判断返回与标准值的比较值用来判断更新二分范围。

未知的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
ll n,m,s,ans;
ll w[N],v[N],L[N],R[N],y[N],dis[N];
bool check(ll x){
	ll now=0;
	fill(y,y+N,0);
	fill(dis,dis+N,0);
	for(int i=1;i<=n;i++){
		if(w[i]>=x){
			y[i]=y[i-1]+v[i];
			dis[i]=dis[i-1]+1;
		}else{
			y[i]=y[i-1];
			dis[i]=dis[i-1];
		}
	}
	for(int i=1;i<=m;i++)now+=(y[R[i]]-y[L[i]-1])*(dis[R[i]]-dis[L[i]-1]);
	ans=min(ans,abs(now-s));
	return now>s;
}
int main(){
	cin>>n>>m>>s;
	ans=s;
	ll l=1e7,r=-1e7;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>v[i];
		l=min(l,w[i]);
		r=max(r,w[i]);
	}
	for(int i=1;i<=m;i++)cin>>L[i]>>R[i];
	while(l<=r){
		ll mid=(l+r)>>1;
		if(check(mid))l=mid+1;
		else r=mid-1;
	}
	cout<<ans<<endl;
	return 0;
}


饥饿的奶牛(洛谷P1868)


题目大意

给定n个区间,可选择任意区间但不能包含重复元素,求经选择后的最多元素个数。

解题思路

对区间用右端点从小到大排序,使用动态规划,\(dp[i]\)表示前i个区间的最多不重复元素数,推出状态转移方程\(dp[i]=max(dp[i-1],dp[i-1]+curlen)\),对\(dp\)转移优化,发现使用二分查找当某一区间的右端点恰好小于当前左端点时满足最优解。

未知的代码
#include<bits/stdc++.h>
using namespace std;
struct node{
	int x,y;
	bool operator<(const node&e)const{
		return y<e.y;
	}
};
int main(){
	int n;
	cin>>n;
	vector<node>d(n+1);
	for(int i=1;i<=n;i++)cin>>d[i].x>>d[i].y;
	sort(d.begin(),d.end());
	vector<int>dp(n+1);
	for(int i=1;i<=n;i++){
		int l=0,r=i;
		while(l<r){
			int mid=(l+r+1)>>1;
			if(d[mid].y<d[i].x)l=mid;
			else r=mid-1;
		}
		dp[i]=max(dp[l]+d[i].y-d[i].x+1,dp[i-1]);
	}
	cout<<dp[n]<<endl;
	return 0;
}

分梨子(洛谷P1493)


题目大意

n个梨子,每个梨子包含\(A_i,B_i\)这两个属性,给定\(C_1,C_2,C_3\)三个参数,\(A_0,B_0\)表示其选出的满足条件的梨子的属性的最小值,求满足\((C_1\times(A_i-A_0)+C_2\times(B_i-B_0)) \leq C_3\)的梨子的最大个数。

解题思路

差分+二分

未知的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e3+5;
int n;
ll c1,c2,c3,m1=0,m2=0;
int dp[N][N];
int ans=0;
int main(){
    cin>>n>>c1>>c2>>c3;
    for (int i=1; i<=n; ++i){
        ll A,B;
        cin>>A>>B;
        m1=max(A,m1);
        m2=max(B,m2);
        int x=A, y=B;
        while (x){
            int l=(c1*(A - x)+c2*B-c3)/c2, r=B + 1;
            if (l < 0) l = 0;
            while (l<r){
                int mid=l+(r-l)/2;
                if (c1*(A - x)+c2*(B - mid) <= c3){
                    y = mid;
                    r = mid;
                }else l = mid + 1;
            }           
            if (y <= B + 1){
                ++dp[x][y];//差分
                --dp[x][B + 1];
            }
            y = B + 1; //这一步必不可少!!
            --x;
        }
    }
    for (int i=0; i <= m1; ++i){
        int sum=0;
        for (int j=0; j <= m2; ++j){
            sum += dp[i][j];//前缀和获得原dp数组
            ans=max(ans,sum);
        }
    }
    cout<<ans<<endl;
    return 0;
}

K-th Number(hdu 6231)


题目大意

给定一个包含n个元素的数组,将数组中长度不小于k的区间中的第k大的数取出并组成一个新数组,求新数组第m大的元素。

解题思路

二分第k大的数,枚举求区间个数。

未知的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,k;
ll m;
int a[N];
bool check(int x){
	ll ans=0;
	int cnt=0;
	for(int i=1,j=1;i<=n;i++){
		if(a[i]>=x)cnt++;
		if(cnt==k){
			ans+=n-i+1;
			while(a[j]<x){
				ans+=n-i+1;
				j++;
			}
			cnt--;
			j++;
		}
	}
	return ans>=m;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		cin>>n>>k>>m;
		for(int i=1;i<=n;i++)cin>>a[i];
		int l=1,r=1e9;
		while(l<r){
			int mid=r-(r-l)/2;
			if(check(mid))l=mid;
			else r=mid-1;
		}
		cout<<l<<endl;
	}
	return 0;
}

Monthly Expense(poj 3273)


题目大意

给出n天中每天的花费,将这n天分为m组,每组中的天数必须是连续的,要使所分组的花费和尽可能小,最后输出各组花费之和中的最大值。

解题思路

对每组花费和进行二分,比较在当前条件下所划分的组数与m,缩小二分范围直到找到最优解。

未知的代码
#include<iostream>
using namespace std;
const int N=1e5+5;
int n,m,cost[N];
bool check(int x){
	int sum=0,cnt=1;
	for(int i=1;i<=n;i++){
		if(sum+cost[i]<=x)sum+=cost[i];
		else{
			sum=cost[i];
			cnt++;
		}
	}
	return cnt<=m;
}
int main(){
	while(cin>>n>>m){
		int l=0,r=0;
		for(int i=1;i<=n;i++){
			cin>>cost[i];
			l=max(l,cost[i]);
			r+=cost[i];
		}
		int mid;
		while(l<=r){
			mid=(l+r)>>1;
			if(check(mid))r=mid-1;
			else l=mid+1;
		}
		cout<<l<<endl;
	}
	return 0;
}


River Hopscotch(poj 3258)


题目大意

和上述跳石头一样

解题思路

不在赘述,区别在给出的石头位置是无序的,需要先排序。

未知的代码
#include<iostream>
#include<algorithm>
using namespace std;
int L,n,m;
const int N=5e4+5;
int d[N];
bool check(int x){
	int cnt=0,p=0;
	for(int i=1;i<=n+1;i++){
		if(d[i]-d[p]<x)cnt++;
		else p=i;
	}
	return cnt<=m;
}
int main(){
	cin>>L>>n>>m;
	for(int i=1;i<=n;i++)cin>>d[i];
	d[n+1]=L;
	sort(d,d+n+1);
	int l=0,r=L,ans;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)){
			ans=mid;
			l=mid+1;
		}else r=mid-1;
	}
	cout<<ans<<endl;
	return 0;
}

Expanding Rods(hdu 6231)


题目大意

长为L的杆受热会膨胀,膨胀长度不超过原长的一半,现用两个墙夹住原长木棍,根据所给参数求加热后杆中心的位移距离。

解题思路

二分位移距离,根据相应公式判断并缩小二分范围,直至达到精度要求。

未知的代码
#include<iostream>
#include<cmath>
#include<iomanip>
using namespace std;
const double eps=1e-5;
double L,n,c;
bool check(double x){
	double s=(1+n*c)*L,r=(4*x*x+L*L)/x/8;
	return 2*r*asin(L/(2*r))<s;
}
int main(){
	while(cin>>L>>n>>c&&(L>=0&&n>=0&&c>=0)){
		double l=0,r=0.5*L;
		while(r-l>eps){
			double mid=(l+r)/2;
			if(check(mid))l=mid;
			else r=mid;
		}
		cout<<fixed<<setprecision(3)<<l<<endl;
	}
	return 0;
}
posted @ 2024-02-26 17:45  Lost-in-love  阅读(62)  评论(0)    收藏  举报