二分初解(二)P1678 烦恼的高考志愿

最近又听老师讲了讲二分算法,有些体悟分享给大家。

这次我们来看一道比较简单的题,也更方便我们理解二分算法:

思路简单说下,这道题不需要check()函数,在二分过程中直接比较就行,不过分析题目可以知道我们需要先将学校排个序,然后在学校录取分最低与最高中找到与每一位学生分数的不满意度最小的一个,给统计不满意度的ans+=就可以,那么显然二分的是学校的分数。

有一点需要注意的是需要在二分前判断下是否有学生的分数比最低的学校录取分数线还低,如果有就要先给ans加上。

先上代码(第一种:while(left<right)):

#include <bits/stdc++.h>
using namespace std;
const int M = 1e6+7;
long long m,n,ans=0;
long long xiao[M],xues[M];
int main(){
	scanf("%d%d",&m,&n);
	for(int i=1; i<=m; i++){//输入学校的分数线
		cin>>xiao[i];
	}
	for(int i=1; i<=n; i++){//学生的
		cin>>xues[i];
	}
	sort(xiao+1,xiao+m+1);//给学校的分数线从小到大排序
	
	for(int i=1; i<=n; i++){//判断如果有小于最低分数线的学生分数就先加
		if(xues[i]<=xiao[1]){
			ans+=xiao[1]-xues[i];
			continue;
		}
		int l=1, r=m+1;
		while(l<r){//二分模板之一
				int mid=l+(r-l)/2;
				if(xiao[mid]<=xues[i]){
					l=mid+1;//如果学校的分数小于学生就左边界向前
				}else {//否则右边
					r=mid;
				}
			}
			ans+=min(abs(xues[i]-xiao[l-1]),abs(xues[i]-xiao[l]));	
	}
	cout<<ans;
	return 0;
}

但是这个代码有一个缺陷:可能无法确定哪一个才是最小的,例如我的学校分数和学生分数刚好相等,则我的l还会加一才能停止循环;或者我这一次的分数和下一个的分数刚好一个大一个小,而这时候便不知道哪一个才是不满意度最小的,所以需要在用一个min()函数,来判断我l-1和l(因为我最终的l是加了一的,也就是说第一个大于学生分数的学校分数)哪一个不满意度最小。

第二种代码(while(left<=right)):

		int l = 1, r = m + 1;
		long long  t = 1e18;
		while (l <= r) {
			int mid = l + (r - l) / 2;
			t = min(t, abs(xiao[mid] - xues[i]));
			if(xiao[mid] == xues[i]) break;
			else if (xiao[mid] < xues[i]) {
				l = mid + 1;
				//cnt=mid;
			} else {
				r = mid - 1;
			}
		}		
		ans += t;

上面的第二种代码,正常来说需要用一个cnt来保存真正的答案,最终循环停止的时候,常理来说没有正解,因为其循环最终的结果为l>r,但这个题并不需要,因为我们需要去比较的其实是大于学生分数和小于学生分数的两个解谁更小,所以只需要再每次循环内都比较一次最小就可以,当然在循环内比较也同样适用于上面的第一种写法。

至于两种写法的真正内涵我还不是很明白,只能告诉大家用法,若明白的时候会再出二分的题解。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值