二分进阶运用

文章讨论了如何在编程中运用前缀和与二分查找解决数组问题,涉及边界确定、排序和优化算法,强调了二分思想在处理复杂问题中的重要性。
摘要由CSDN通过智能技术生成

题目1:

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
int n, m;
int a[100001];
long long s[100001];
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	s[0] = 0;
	for (int i = 1; i <= n; i++)
		s[i] = s[i - 1] + a[i];
	for (int i = 1; i <= m; i++) {
		int l;
		long long x;
		cin >> l >> x;
		int L = l - 1, R = n + 1;
		while (L + 1 < R) {
			int M = (L + R) / 2;
			if (s[M] - s[l - 1] <= x)
				L = M;
			else
				R = M;
		}
		if (l > L) cout << "-1" << endl;
		else cout << L << endl;
	}
	return 0;
}

上述代码是一道前缀和与二分的综合运用,其中我们对于每一个询问,首先确定两个边界,我们每组询问要找的数字即为L,不过记得考虑输出-1时的情况,

第二道题:

 

 上述问题对于每个询问,需要考虑使用两次二分找出两个边界,其中一个边界时大于等于l的边界而另一个则是小于等于r的边界,二者作差加1就是输出的第一个数字,利用前缀和在算出边界之间所有数字的和即可。

#include<iostream>
#include<algorithm>
using namespace std;
int n,m,a[100001];
long long s[100001];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	sort(a+1,a+n+1);
	s[0]=0;
	for(int i=1;i<=n;i++)
	s[i]=s[i-1]+a[i];
	for(int i=1;i<=m;i++){
		int l,r;
		cin>>l>>r;
		int L1=0,R1=n+1;
		while(L1+1<R1){
			int M=(L1+R1)/2;
			if(a[M]<l)
			L1=M;
			else
			R1=M;
		}
		int L2=0,R2=n+1;
		while(L2+1<R2){
		int M=(L2+R2)/2;
		if(a[M]<=r)
		L2=M;
		else 
		R2=M;
		}
		cout<<L2-R1+1<<' '<<s[L2]-s[R1-1]<<endl;
	}
	return 0;
}

其实由上述的二分部分我总结到:得到的L刚刚好是其对应的if条件里面部分的最后一个,r则是l后面一个。

同时二分也不仅仅只能用在数组问题里面,在没有引入数组有时候也可以进行使用

第三题:二分练习

 代码解答:

#include<iostream>
using namespace std;
int n,m,k[100001],b[100001];
long long calc(int x){
	long long y=0;
	for(int i=1;i<=n;i++)
	if(b[i]<=x)
	y+=(x-b[i])/k[i]+1;
	return y;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>k[i]>>b[i];
	cin>>m;
	int L=0,R=1<<23;
	while(L+1<R){
		int M=(L+R)/2;
		if(calc(M)<m)
		L=M;
		else 
		R=M;
	}
	cout<<R;
	return 0;
}

这道题相对来说较为难考虑到使用二分方法进行解答,对于这个题使用二分的思路主要是我们使用一个函数,对于传入的x,我们计算每个序列中小于x的数字个数并挨个相加,得到所有序列里面小于等于x的个数和,同时利用二分加函数的形式,由于我们要求的是最后合并序列之中第m小的数字那么我们这时候的分界线的左边就对应m-1个小于m个数字而右边也就是R就对应着刚刚好当calc(M)>=m 的时候的calc(M)的值从而最终得到想要的结果R。

第四道题:

#include<iostream>
using namespace std;
int n;
long long k;
int a[100001];
long long calc(long long x){
	long long y=0;
	for(int i=1;i<=n;i++)
	if(a[i]<=x)
	y+=x-a[i];
	return y;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	long long L=1,R=(long long)(1e13)+(long long)(1e8)+1;
    while(L+1<R){
    	long long M=(L+R)/2;
    	if(calc(M)<=k)
    	L=M;
    	else 
    	R=M;
	}
	cout<<L<<endl;
	return 0;
}

这道题目和第三题有类似的地方,大致的思路就是,首先根据题意,我们可以思考得出我们应该尽可能的消耗完给你的k次数,因为多使用次数一定不会让结果变小,这样我们二分的分界线用来分什么就能考虑出来了,就是我们需要求出在k次以内的操作中最后得到的序列的最小值的最大值,那么二分的分界线左边对应的就是在k次操作以内满足条件得到的所有数字,而右边就是需要k次操作以上才可以得到的最小值的最大值。

总结以上给我的感受就是很多时候二分的思想貌似不那么的容易想到要使用,或许后续还需要加强练习不断加强自己对二分这一思想的理解同时能够见更多的题型让自己以后在更多更难的题目上面能够用二分这一方法让代码的时间复杂度降低从而优化算法。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

残念亦需沉淀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值