二分查找、二分答案

目录

P2249 【深基13.例1】查找

P1102 A-B 数对

P1824 进击的奶牛

P1577 切绳子

P1873 [COCI 2011/2012 #5] EKO / 砍树

P2440 木材加工

P1678 烦恼的高考志愿——方法1:手写二分

P1678 烦恼的高考志愿——方法2:lower_bound

P1678 烦恼的高考志愿——方法3:upper_bound

P1163 银行贷款——方法1:暴力枚举

P1163 银行贷款——方法2:二分答案

P2678 [NOIP2015 提高组] 跳石头

P1182 数列分段 Section II

P3853 [TJOI2007]路标设置

P3743 kotori的设备

P1024 [NOIP2001 提高组] 一元三次方程求解

P1314 [NOIP2011 提高组] 聪明的质监员

P1083 [NOIP2012 提高组] 借教室

P1843 奶牛晒衣服

P1281 书的复制

P1883 函数

P4343 [SHOI2015]自动刷题机


P2249 【深基13.例1】查找

//找最先出现的

#include <bits/stdc++.h>
using namespace std;
int n, m, a[1000010], q, l, r, mid;
int main()
{
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
	}
	while(m--){
		scanf("%d", &q);
		l=1;
		r=n;
		while(l<r){
			mid=(l+r)/2;
			if(a[mid]>=q){	//找到靠前的 
				r=mid;
			}
			else{	//a[mid]<q
				l=mid+1;
			}
		}
		if(a[l]==q){
			printf("%d ", l);
		}
		else{
			printf("%d ", -1);
		}
	}
	return 0;
}

//找最后出现的

#include <bits/stdc++.h>
using namespace std;
int n, m, a[1000010], q, l, r, mid;
int main()
{
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
	}
	while(m--){
		scanf("%d", &q);
		l=1;
		r=n;
		while(l<r){
			//当l+1=r时, 如果mid=(l+r)/2=l, 而不是mid=(l+r+1)/2=r 
			//如果进入if分支, 会出现l=mid=l, 产生死循环 
			mid=(l+r+1)/2;
			if(a[mid]<=q){	//找到靠后的 
				l=mid;
			}
			else{	//a[mid]>q
				r=mid-1;
			}
		}
		if(a[l]==q){
			printf("%d\n", l);
		}
		else{
			printf("%d\n", -1);
		}
	}
	return 0;
}

//lower_bound

#include <bits/stdc++.h>
using namespace std;
int n, m, a[1000010], q, id;
int main()
{
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
	}
	while(m--){
		scanf("%d", &q);
		//找到第一个大于等于q的 
		id=lower_bound(a+1, a+n+1, q)-a; 
		if(a[id]==q){
			printf("%d ", id);
		} 
		else{
			printf("-1 ");
		}
	}
	return 0;
}

P1102 A-B 数对

//map

#include <bits/stdc++.h>
using namespace std;
int n, c;
int a[200000];
long long result;
map<int, int> m;

int main() 
{
	cin >> n >> c;
	for(int i=0; i<n; i++){
		cin >> a[i];
		m[a[i]]++;
		a[i]-=c; //a[i]+=c;也行
	}
	
	for(int i=0; i<n; i++){
		result+=m[a[i]];
	}
	
	cout << result;
	return 0;
}

//二分查找

#include<bits/stdc++.h>
using namespace std;
int a[200005],b[200005];
long long num[200005], res=0;
int n,c,cnt=1;
int main(){
	cin>>n>>c;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	num[1] = 1; b[1] = a[1];
	for(int i=2;i<=n;i++){
		if(a[i]!=a[i-1]){
			b[++cnt]=a[i];
			num[cnt]=1;
		}
		else num[cnt]++;
	}
	for(int i=1;i<=cnt;i++){
		int l=1, r=cnt; 
		int x = c + b[i];
		while(l<r){
			int mid=(l+r)/2;
			if(x <= b[mid]) r = mid;
			else l = mid+1;
		}
		if(b[l] == x) res += num[i] * num[l];
	}
	cout<<res<<endl;
	return 0;
}

P1824 进击的奶牛

#include <bits/stdc++.h>
using namespace std;
int n, a[100010], asd[100010], c, l=1e9, r=1e9, mid; 
//检验所有牛的间距是否都大于等于x
//转换为依次保证牛的间距大于等于x, 看能否放下c头牛 
bool check(int x)
{
	int num=0;	//满足间距大于等于x时, 能放的牛的数量 
	int temp=0;	//当前牛棚距离前一个放牛的牛棚的间距 
	//枚举牛棚 
	for(int i=1; i<=n; ++i){
		temp+=asd[i];	//加上第i个牛棚和第i-1个牛棚的间距 
		if(temp>=x){	//如果大于等于x了, 说明第i个牛棚可以放牛 
			temp=0;		//间距归零	
			num++;		//能放的牛数++ 
			if(num>=c){	
				return true;	//减少一些循环次数 
			}
		} 
	} 
	return false;
}
int main()
{
	scanf("%d %d", &n, &c);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);			//输入每个牛棚的坐标 
	}
	sort(a+1, a+n+1);				//排序, 题目没说有序 
	for(int i=2; i<=n; ++i){
		asd[i]=a[i]-a[i-1];			//第i个牛棚距离前一个牛棚的间距 
		l=min(l, asd[i]);			//最近距离的左边界 
	}
	asd[1]=1e9;
	while(l<r){
		mid=(l+r+1)/2;
		if(check(mid)){				//可以满足所有牛(c头)的距离都大于等于mid, 说明最近距离可能更大 
			l=mid;					//右半部分找答案 
		}
		else{						//无法满足所有牛(c头)的距离都大于等于mid, 说明最近距离达不到mid 
			r=mid-1;				//左半部分找答案 
		}
	}
	printf("%d", l);
	return 0;
}

P1577 切绳子

100分做法,避免精度问题,将原属于扩大100倍,巧妙处理舍掉小数

#include <bits/stdc++.h>
using namespace std;
int n, k;
double a[100010], l, r, mid, asd;
//判断切割后每条绳子的长度为ans时, 能否切割出k条绳子 
bool check(double ans)
{
	int total=0;
	for(int i=1; i<=n; ++i){
		total+=a[i]/ans;	//第i根绳子的长度除以切割的长度 
		if(total>=k){		//如果已经够k条绳子, 直接返回true 
			return true;	//能切割出k条绳子 
		}	
	}
	return false;			//切割不出k条绳子 
}
int main()
{ 
	scanf("%d %d", &n, &k);
	//给定的n条绳子长度 
	for(int i=1; i<=n; ++i){
		scanf("%lf", &a[i]);
		a[i]*=100;
		r=max(r, a[i]);	//初始化二分查找右边界
	}
	//初始化二分查找左边界 
	l=0;
	//二分查找 
	while(l<r){
		mid=(l+r+1)/2;
		if(check(mid)){	//可以割出k条长度为mid的绳子 
			l=mid; 
		}
		else{	//割不出k条长度为mid的绳子  
			r=mid-1;
		}
	}
	printf("%lf", l/100);    //注意,不是保留两位小数

	return 0;
}

77分做法,没直接舍掉后面的小数

#include <bits/stdc++.h>
using namespace std;
int n, k;
double a[100010], l, r, mid;
//判断切割后每条绳子的长度为ans时, 能否切割出k条绳子 
bool check(double ans)
{
	int total=0;
	for(int i=1; i<=n; ++i){
		total+=int(a[i]/ans);	//第i根绳子的长度除以切割的长度 
		if(total>=k){		//如果已经够k条绳子, 直接返回true 
			return true;	//能切割出k条绳子 
		}	
	}
	return false;			//切割不出k条绳子 
}
int main()
{
	scanf("%d %d", &n, &k);
	//给定的n条绳子长度 
	for(int i=1; i<=n; ++i){
		scanf("%lf", &a[i]);	
	}
	sort(a+1, a+n+1);	//对给定的n条绳子从小到大排序 
	//初始化二分查找左边界, 注意不能为0 
	l=0.001;
	//初始化二分查找右边界
	r=a[n];
	//二分查找 
	while(l+0.001<r){
		mid=(l+r)/2;
		if(check(mid)){	//可以割出k条长度为mid的绳子 
			l=mid; 
		}
		else{	//割不出k条长度为mid的绳子  
			r=mid-0.001;
		}
	}
	printf("%.2lf", l);    //题目要求舍掉, 这样会四舍五入
	return 0;
}

93分做法,double精度问题

#include <bits/stdc++.h>
using namespace std;
int n, k;
double a[100010], l, r, mid;
//判断切割后每条绳子的长度为ans时, 能否切割出k条绳子 
bool check(double ans)
{
	int total=0;
	for(int i=1; i<=n; ++i){
		total+=int(a[i]/ans);	//第i根绳子的长度除以切割的长度 
		if(total>=k){		//如果已经够k条绳子, 直接返回true 
			return true;	//能切割出k条绳子 
		}	
	}
	return false;			//切割不出k条绳子 
}
int main()
{ 
	scanf("%d %d", &n, &k);
	//给定的n条绳子长度 
	for(int i=1; i<=n; ++i){
		scanf("%lf", &a[i]);	
	}
	sort(a+1, a+n+1);	//对给定的n条绳子从小到大排序 
	//初始化二分查找左边界, 注意不能为0 
	l=0.001;
	//初始化二分查找右边界
	r=a[n];
	//二分查找 
	while(l+0.001<r){
		mid=(l+r)/2;
		if(check(mid)){	//可以割出k条长度为mid的绳子 
			l=mid; 
		}
		else{	//割不出k条长度为mid的绳子  
			r=mid-0.001;
		}
	}
	//四舍五入77分 
//	printf("%.2lf", l);
//	printf("%.2lf\n", 2.238);	//输出2.24 
//	printf("%.2lf", 2.234);		//输出2.23
	printf("%d.", int(l));
	l=l-int(l);
	l*=10;
	printf("%d", int(l));
	l=l-int(l);
	l*=10;
	printf("%d", int(l));
	return 0;
}

100分做法,输出r,但其实不应该

#include <bits/stdc++.h>
using namespace std;
int n, k;
double a[100010], l, r, mid;
//判断切割后每条绳子的长度为ans时, 能否切割出k条绳子 
bool check(double ans)
{
	int total=0;
	for(int i=1; i<=n; ++i){
		total+=int(a[i]/ans);	//第i根绳子的长度除以切割的长度 
		if(total>=k){		//如果已经够k条绳子, 直接返回true 
			return true;	//能切割出k条绳子 
		}	
	}
	return false;			//切割不出k条绳子 
}
int main()
{ 
	scanf("%d %d", &n, &k);
	//给定的n条绳子长度 
	for(int i=1; i<=n; ++i){
		scanf("%lf", &a[i]);	
	}
	sort(a+1, a+n+1);	//对给定的n条绳子从小到大排序 
	//初始化二分查找左边界, 注意不能为0 
	l=0.001;
	//初始化二分查找右边界
	r=a[n];
	//二分查找 
	while(l+0.001<r){
		mid=(l+r)/2;
		if(check(mid)){	//可以割出k条长度为mid的绳子 
			l=mid; 
		}
		else{	//割不出k条长度为mid的绳子  
			r=mid-0.001;
		}
	}
	//四舍五入77分 
//	printf("%.2lf", l);
//	printf("%.2lf\n", 2.238);	//输出2.24 
//	printf("%.2lf", 2.234);		//输出2.23
	l=r; 
	printf("%d.", int(l));
	l=l-int(l);
	l*=10;
	printf("%d", int(l));
	l=l-int(l);
	l*=10;
	printf("%d", int(l));
	return 0;
}

P1873 [COCI 2011/2012 #5] EKO / 砍树

#include <bits/stdc++.h>
using namespace std;
int n, m, h, a[1000010];
int l, r, mid;
//检验锯片高度为hight时, 能否得到M米的木材 
//时间复杂度O(n), 10^6 
bool check(int hight)
{
	long long sum=0;
	for(int i=1; i<=n; ++i){
		if(a[i]>hight){
			sum+=a[i]-hight;
		}
	}
	if(sum>=m){
		return true;
	}
	else{
		return false;
	}
} 
int main()
{
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
		//锯片的最大高度 
		r=max(r, a[i]);
	}
	//锯片的最小高度 
	l=0;
	while(l<r){
		mid=(l+r+1)/2;
		//锯片高度为mid时,可以锯出m的木材 
		if(check(mid)){
			//尝试升高锯片高度 
			l=mid; 
		} 
		else if(check(mid)==false){ 
			//锯片高度为mid时,无法锯出m的木材
			//只能降低锯片高度
			r=mid-1; 
		}
	}
	printf("%d", r);
	return 0;
}

P2440 木材加工

#include <bits/stdc++.h>
using namespace std;
int n, k, a[100010];
int lef, righ, mid;
long long sum;
//检验锯片高度为hight时, 能否得到M米的木材 
//时间复杂度O(n), 10^6 
bool check(int l)
{
	long long total=0;
	for(int i=1; i<=n; ++i){
		total+=a[i]/l;
		if(total>=k){
			return true;
		}
	}
	return false;
} 
int main()
{
	scanf("%d %d", &n, &k);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
		sum+=a[i];
		//righ表示切出小段可能的最大值 
		righ=max(righ, a[i]);
	}
	//要得到小段的数量大于总长度, 则无法切出 
	if(k>sum){
		printf("0");
		return 0;
	}
	lef=1;
	while(lef<righ){
		mid=(lef+righ+1)/2;
		//小段为mid时, 能够切出k根 
		if(check(mid)){
			//尝试增加小段的长度 
			lef=mid; 
		} 
		else if(check(mid)==false){ 
			//小段为mid时, 无法切出k根及以上 
			//只能减少小段的长度 
			righ=mid-1; 
		}
	}
	printf("%d", righ);
	return 0;
}

P1678 烦恼的高考志愿——方法1:手写二分

#include <bits/stdc++.h>
using namespace std;
int m, a[100010], n, b[100010], l, r, mid, x;
long long ans;
bool flag;
int main()
{
	//学校数、学生数 
	scanf("%d %d", &m, &n);
	//m个学校的分数线 
	for(int i=1; i<=m; ++i){
		scanf("%d", &a[i]);
	}
	//n个学生的估分 
	for(int i=1; i<=n; ++i){
		scanf("%d", &b[i]);
	}  
	//对学校按分数线从低到高排序 
	sort(a+1, a+m+1);
	//枚举每个学生的估分 
	for(int i=1; i<=n; ++i){
		x=b[i];
		if(x<=a[1]){
			ans+=a[1]-x;
			continue;
		}
		else if(x>=a[m]){
			ans+=x-a[m];
			continue;
		}
		//flag如果为true, 表示恰好有学校录取线等于估分 
		flag=false;	
		//二分查找第一所录取线大于等于该分数的学校 
		l=1;
		r=m;
		while(l<r){
			mid=(l+r)/2;
			if(a[mid]==x){
				flag=true;
				break; 
			}
			else if(a[mid]>x){	//最终找到的可能是mid 
				r=mid;
			}
			else{	//最终找到的不可能是mid 
				l=mid+1;
			}
		}
		if(flag){
			continue;
		}
		//比较哪个学校录取线和该同学的估分更接近
		if(abs(a[l-1]-x)<abs(a[l]-x)){
			ans+=abs(a[l-1]-x);
		}
		else{
			ans+=abs(a[l]-x);
		}		 
	} 
	printf("%lld", ans);
	return 0;
}

P1678 烦恼的高考志愿——方法2:lower_bound

#include <bits/stdc++.h>
using namespace std;
int m, a[100010], n, b[100010], id, x;
long long ans;
bool flag;
int main()
{
	//学校数、学生数 
	scanf("%d %d", &m, &n);
	//m个学校的分数线 
	for(int i=1; i<=m; ++i){
		scanf("%d", &a[i]);
	}
	//n个学生的估分 
	for(int i=1; i<=n; ++i){
		scanf("%d", &b[i]);
	}  
	//对学校按分数线从低到高排序 
	sort(a+1, a+m+1);
	//枚举每个学生的估分 
	for(int i=1; i<=n; ++i){
		x=b[i];
		if(x<=a[1]){
			ans+=a[1]-x;
			continue;
		}
		else if(x>=a[m]){
			ans+=x-a[m];
			continue;
		}
		//在a数组中找到第一个大于等于x的下标 
		id=lower_bound(a+1, a+m+1, x)-a;
		//id为第一个大于等于x的位置, 则id-1为第一个小于x的位置 
		//比较哪个学校录取线和该同学的估分更接近
		if(abs(a[id-1]-x)<abs(a[id]-x)){
			ans+=abs(a[id-1]-x);
		}
		else{
			ans+=abs(a[id]-x);
		}		 
	} 
	printf("%lld", ans);
	return 0;
}

P1678 烦恼的高考志愿——方法3:upper_bound

#include <bits/stdc++.h>
using namespace std;
int m, a[100010], n, b[100010], id, x;
long long ans;
bool flag;
int main()
{
	//学校数、学生数 
	scanf("%d %d", &m, &n);
	//m个学校的分数线 
	for(int i=1; i<=m; ++i){
		scanf("%d", &a[i]);
	}
	//n个学生的估分 
	for(int i=1; i<=n; ++i){
		scanf("%d", &b[i]);
	}  
	//对学校按分数线从低到高排序 
	sort(a+1, a+m+1);
	//枚举每个学生的估分 
	for(int i=1; i<=n; ++i){
		x=b[i];
		if(x<=a[1]){
			ans+=a[1]-x;
			continue;
		}
		else if(x>=a[m]){
			ans+=x-a[m];
			continue;
		}
		//在a数组中找到第一个大于x的下标 
		id=upper_bound(a+1, a+m+1, x)-a;
		//id为第一个大于x的位置, 则id-1为第一个小于等于x的位置 
		//比较哪个学校录取线和该同学的估分更接近
		if(abs(a[id-1]-x)<abs(a[id]-x)){
			ans+=abs(a[id-1]-x);
		}
		else{
			ans+=abs(a[id]-x);
		}		 
	} 
	printf("%lld", ans);
	return 0;
}

P1163 银行贷款——方法1:暴力枚举

#include <bits/stdc++.h>
using namespace std;
//贷款总金额、每月还款金额、分期付款还清需要的月数
double total, permonth, n;		 
double ans, temp1, temp2;
int main()
{
	scanf("%lf %lf %lf", &total, &permonth, &n);
	for(double ans=0; ans<=6; ans+=0.0001){
		//ans月利率
		//计算月利率为当前ans时, n个月结束后未还金额 
		temp1=total;
		for(int i=1; i<=n; ++i){
			temp1=temp1*(1+ans);
			temp1-=permonth;
		} 
		//计算月利率为当前ans+0.0001时, n个月结束后未还金额 
		temp2=total;
		for(int i=1; i<=n; ++i){
			temp2=temp2*(1+ans+0.0001);
			temp2-=permonth;
		} 
		//如果月利率为ans时未还金额小于等于0, 月利率为ans+0.0001时未还金额大于等于0
		//找到答案了 
		if(temp1<=0 && temp2>=0){
			printf("%.1lf", ans*100);	//答案为ans, 因为ans一定能还完, ans+0.0001可能还不完 
			break;
		}
	} 
	return 0;
}

P1163 银行贷款——方法2:二分答案

#include <bits/stdc++.h>
using namespace std;
//贷款总金额、每月还款金额、分期付款还清需要的月数
double total, permonth, n;		 
double l, r, mid, temp;
int main()
{
	scanf("%lf %lf %lf", &total, &permonth, &n);
	l=0;
	r=6;
	while(l+0.0001<r){
		mid=(l+r)/2;
		//mid月利率
		//计算月利率为当前mid时, n个月结束后未还金额 
		temp=total;
		for(int i=1; i<=n; ++i){
			temp=temp*(1+mid);
			temp-=permonth;
		} 
		//能还完, 利率低了 
		if(temp<0){
			l=mid; 
		} 
		else if(temp>=0){	//还不完, 利率高了 
			r=mid-0.0001; 
		}
		else if(temp==0){
			break;
		}
	}
	printf("%.1lf", l*100);		//l能还完 
	return 0;
}

附3组测试数据

P1163_3.in

1 1 1

P1163_3.out

0.0

P1163_4.in

1000000000 2147483647 123

P1163_4.out

214.7

P1163_5.in

443423 23477 3767

P1163_5.out

5.3

P2678 [NOIP2015 提高组] 跳石头

#include <bits/stdc++.h>
using namespace std;
int dis, n, m, d[50010], l=1e9, r, mid, a[50010];
//判断最短跳跃距离为x时, 能否满足条件(至多移走m块岩石) 
bool check(int x)
{
	int asd=0, before=0;
	for(int i=1; i<=n+1; ++i){
		if(a[i]+before<x){	//从第i-1块石头跳到第i块石头的距离小于最短跳跃距离x了, 需要移走石头 
			 asd++; 
			 before+=a[i];	
		}
		else{
			before=0;
		}
		if(asd>m){			//如果需要移走的岩石数大于m, 返回false 
			return false;
		} 
	}
	return true;
}
int main()
{
	scanf("%d %d %d", &dis, &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &d[i]);	//d[i]表示第i块石头到起点的距离 
		a[i]=d[i]-d[i-1];	//a[i]表示从第i-1块石头跳到第i块石头的距离 
		l=min(l, a[i]);		//找左边界的 
	}
	a[n+1]=dis-d[n];	
	l=min(l, a[n+1]);
	r=dis;					//初始化右边界 
	while(l<r){	
		mid=(l+r+1)/2;	//最短跳跃距离的最大值为mid 
		if(check(mid)){	//最短跳跃距离为mid时, 需要移走的岩石不超过m 
			l=mid;		 
		}
		else{			//最短跳跃距离为mid时, 需要移走的岩石超过m 
			r=mid-1;	//mid不可能是一个答案 
		}
	}
	printf("%d", l);
	return 0;
}

P1182 数列分段 Section II

#include <bits/stdc++.h>
using namespace std;
int n, m, l, r, mid, a[100010];
//判断每段和的最大值为x时, 能否将数列分成m段 
bool check(int x)
{
	int cursum=0, curnum=0;		//当前这一段的和, 总共有多少段 
	for(int i=1; i<=n; ++i){
		if(cursum+a[i]<x){	//将a[i]扩进来后当前段的和小于x, 有望将下一个整数也扩到这一段中 
			cursum+=a[i];
		}
		else if(cursum+a[i]>x){		//将a[i]扩进来后当前段的和大于x了, 这一段不能把a[i]扩进来 
			curnum++;				//段数++ 
			cursum=a[i]; 			//a[i]必须重开一段 
			if(cursum>x){			//如果a[i]大于每段和的最大值x, 与x为每段和的最大值矛盾。 
				return false;
			}
		}
		else if(cursum+a[i]==x){	//如果将a[i] 扩进来恰好够x, 则这一段刚好 
			curnum++;				//段数++ 
			cursum=0;				//下一段重新开始 
		}
	}
	//处理最后一段 
	if(cursum>0){					//如果最后一段不为0 
		curnum++;					//成一段 
	}
	//能合在一段就合在一段的情况下, 得到的段数curnum小于等于m, 则一定能拆成m段。
	return curnum<=m;				 
}
int main()
{
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);
		l=max(l, a[i]);		//左边界, 每段和的最大值的最小, 至少要大于等于每个整数, 即a[i] 
	}
	r=1e9;		//右边界 
	while(l<r){
		mid=(l+r)/2;	//每段最大和为mid 
		if(check(mid)){	//如果每段最大和都不超过mid, 恰好能分成m段 
			r=mid; 		//题目要求的是每段和的最大值的最小, 答案在mid左半部分, 且包含mid 
		}
		else{	//每段最大和不超过mid, 无法分成m段。或者有的段已经超过mid了 
			l=mid+1;	//每段的最大和为mid时无法分成m段, 说明答案在mid右侧, 且不包含mid	
		}
	}
	printf("%d", l);
	return 0;
}

附1组测试数据

P1182_1.in

6 3
4 2 4 5 1 1

P1182_1.out

7

P3853 [TJOI2007]路标设置

#include <bits/stdc++.h>
using namespace std;
int dis, n, k;	//dis公路的长度、n原有路标的数量、最多可增设的路标数量
int a[100010], asd[100010]; 
int l, r, mid, temp;
//相邻路标的最大距离定义为该公路的 空旷指数 
//求:增设路标后能达到的最小 空旷指数 值
//判断为达到空旷指数为x, 即相邻路标距离不能超过x, 需要增设的路标数量是否超过k
//如果没超过k, 则可以, 如果超过了k, 则不可以, 返回false 
bool check(int x){
	int num=0;
	for(int i=1; i<=n+1; ++i){
		temp=asd[i];	//需要备份一下 
		while(temp>x){	//第i个路标距离前一个位置(i-1)超过了x, 需要增设路标 
			num++;
			//增设的位置应该尽量靠后, 直接设在距离上一路标x处 
//			int site=a[i-1]+x; 
			temp-=x;	//不能直接对asd[i]进行操作
			if(num>k){	//如果增设的路标数超过k, 返回false 
				return false;
			} 
		}
	}
	return  true;
} 
int main()
{
	scanf("%d %d %d", &dis, &n, &k);
	for(int i=1; i<=n; ++i){
		scanf("%d", &a[i]);		//第i个路标距离起点的距离 
		asd[i]=a[i]-a[i-1];
		r=max(r, asd[i]);
	}
	asd[n+1]=dis-a[n];
	l=0;
	r=max(r, asd[n+1]);
	//二分空旷指数 
	while(l<r){
		mid=(l+r)/2;
		if(check(mid)){	//可以通过增设不超过k个路标, 满足相邻路标的距离均不超过mid 
			r=mid;		//答案在左半部分, 且包含mid 
		} 
		else{			//增设k个路标, 无法满足相邻路标的距离不超过mid 
			l=mid+1; 			//答案在右半部分, 且不包含mid 
		} 
	} 
	printf("%d", l);
	return 0;
}

P3743 kotori的设备

#include <bits/stdc++.h>
using namespace std;
const double eps=1e-5;    
int n, p;	//设备数、每秒可以充能的数量 
int a[100010], b[100010];	//每秒消耗的能量、初始能量 
long long suma, sumb;		//每秒消耗的总能量 
double l, r=1e10, mid;	
double ans;
//看设备能否坚持x秒 
bool check(double x)
{
	double need=0;
	for(int i=1; i<=n; ++i){
		//x秒内设备i消耗的能量大于设备i初始的能量 
		if(a[i]*x>b[i]){
			need+=a[i]*x-b[i];	//需要充能 
		}
	} 
	return p*x>=need;	//如果x秒内充能量满足需求, 返回true, 否则返回false 
}
int main()
{
	scanf("%d %d", &n, &p);
	for(int i=1; i<=n; ++i){
		scanf("%d %d", &a[i], &b[i]);
		suma+=a[i];
		sumb+=b[i];
	}
	//可以无限使用 
	if(p>=suma){	//每秒充能大于等于每秒耗能 		//能得15分 
		printf("-1");
		return 0;
	}
	while(l+eps<r){
//		mid=(l+r)/2;	//也能AC 
		mid=(l+r+eps)/2;
		if(check(mid)){		//能坚持mid秒, 尝试更多大秒数, 解在右半部分 
			l=mid;
		}
		else{				//坚持不了mid秒, 尝试更小的秒数, 解在左半部分 
			r=mid-eps;
		}
	}
	printf("%.10lf", l);
	return 0;
}

P1024 [NOIP2001 提高组] 一元三次方程求解

#include <bits/stdc++.h>
using namespace std;

double a, b, c, d;
int cnt;
double cal(double x){
	return (a*x*x*x + b*x*x + c*x +d);
}
int main()
{
    cin >> a >> b >> c >> d;
    
	for(double i=-100; i<100; i+=0.01){
//		if(cnt==3)	break;
		if(abs(cal(i))<=0.01){
			printf("%.2f ", i);
			cnt++;
		}
	} 
	
    return 0;
}

P1314 [NOIP2011 提高组] 聪明的质监员

暴力代码, 能得35分

//10分暴力代码 
#include <bits/stdc++.h>
using namespace std;
int n, m, mid, ql[200010], qr[200010];
long long w[200010], v[200010], wsum[200010], vsum[200010], l=1e6, r, s, y, ans;
bool flag;
int main()
{
	scanf("%d %d %lld", &n, &m, &s);
	ans=s;
	for(int i=1; i<=n; ++i){
		scanf("%lld %lld", &w[i], &v[i]);
		l=min(l, w[i]);
		r=max(r, w[i]);
	}
	for(int i=1; i<=m; ++i){
		scanf("%d %d", &ql[i], &qr[i]);
	}
	//6*10^11
	//暴力枚举每一个可能的w 
	for(int i=l; i<=r; ++i){		//10^6
		y=0; 
		memset(wsum, 0, sizeof(wsum));
		memset(vsum, 0, sizeof(vsum));
		flag=false;
		//求前缀和wsum
		for(int j=1; j<=n; ++j){	//2*10^5 
			if(w[j]>=i){
				wsum[j]=wsum[j-1]+1;
			}
			else{
				wsum[j]=wsum[j-1];
			} 
		}
		//求前缀和vsum 
		for(int j=1; j<=n; ++j){	//2*10^5
			if(w[j]>=i){
				vsum[j]=vsum[j-1]+v[j];
			}
			else{
				vsum[j]=vsum[j-1];
			}
		}
		//计算 
		for(int j=1; j<=m; ++j){	//2*10^5
			int lef=ql[j];		//检验区间的左边界 
			int righ=qr[j];		//检验区间的右边界 
			y+=(wsum[righ]-wsum[lef-1])*(vsum[righ]-vsum[lef-1]);
			if(y>2*s){
				flag=true;
				break;
			}
		} 
		if(flag){
			continue;
		}
		if(abs(s-y)<ans){
			ans=abs(s-y);
			if(ans==0){
				break;
			}
		}
	}
	printf("%lld", ans);
	return 0;
}

AC代码

#include <bits/stdc++.h>
using namespace std;
int n, m, ql[200010], qr[200010];
long long w[200010], v[200010], wsum[200010], vsum[200010], l=1e6, r, mid, s, y, ans;
//计算W为x时, 得到的y 
long long cal(int x)
{
	y=0; 
	memset(wsum, 0, sizeof(wsum));
	memset(vsum, 0, sizeof(vsum));
	//前缀和 
	for(int i=1; i<=n; ++i){	//2*10^5 
		if(w[i]>=x){
			wsum[i]=wsum[i-1]+1;	//求前缀和wsum
			vsum[i]=vsum[i-1]+v[i];	//求前缀和vsum 
		}
		else{
			wsum[i]=wsum[i-1];	//求前缀和wsum
			vsum[i]=vsum[i-1];	//求前缀和vsum 
		} 
	}
	//计算 
	for(int i=1; i<=m; ++i){	//2*10^5
		int lef=ql[i];		//检验区间的左边界 
		int righ=qr[i];		//检验区间的右边界 
		y+=(wsum[righ]-wsum[lef-1])*(vsum[righ]-vsum[lef-1]);
	} 
	return y; 
} 
int main()
{
	scanf("%d %d %lld", &n, &m, &s);
	ans=s;
	for(int i=1; i<=n; ++i){
		scanf("%lld %lld", &w[i], &v[i]);
		l=min(l, w[i]);
		r=max(r, w[i]+1);
	}
	for(int i=1; i<=m; ++i){
		scanf("%d %d", &ql[i], &qr[i]);
	}
	//找到第一个让y大于等于s的位置 
	while(l<r){
		mid=(l+r+1)/2;
		y=cal(mid);
		if(y>s){	//如果W取mid, 得到的y大于s, 那么尝试将W取大一些, 这样才能让y变小一些, 看能否更逼近s 
			l=mid; 
		}
		else if(y<s){//如果W取mid, 得到的y小于s, 那么尝试将W取小一些, 这样才能让y变大一些, 看能否更逼近s
			r=mid-1; 
		}
		else{	//如果y等于s, 直接输出0, 已经是最小了 
			printf("0");
			return 0;
		}
		ans=min(ans, abs(s-y));		//方法2的计算 
	}
//	printf("%lld", abs(cal(l)-s));		//会出错,因为s的两端都有可能  
	
//	printf("%lld", min(abs(cal(l)-s), abs(cal(l+1)-s)));	//方法1的输出 
	printf("%lld", ans);	//方法2的输出 
	return 0;
}

附1组测试数据

P1314_1.in

10 10 1475400
23954 25180
18805 2701
17195 5663
7044 13659
8139 30927
19774 25516
7472 4572
5999 6166
1185 13621
10414 26521
2 10
4 7
5 8
1 6
2 7
1 3
2 7
3 4
1 6
1 10

P1314_1.out

27196

P1083 [NOIP2012 提高组] 借教室

#include <bits/stdc++.h>
using namespace std;
int n, m, l, r, mid;
int a[1000001], d[1000001], s[1000001], t[1000001], diff[1000001];
bool check(int x)
{
	memset(diff, 0, sizeof diff);
	//差分 
	for(int i=0; i<x; i++)
	{
		diff[s[i]-1]-=d[i];
		diff[t[i]]+=d[i];	
	}
	
	for(int i=1; i<n; i++){
		//前缀和 
		diff[i]+=diff[i-1];
	}
	for(int i=0; i<n; i++){
		if(a[i]+diff[i]<0){
			return false;
		}
	}
	return true;
}
int main()
{
	cin >> n >> m;
	l=0;
	r=m;
	for(int i=0; i<n; i++){
		cin >> a[i];
	}
	for(int i=0; i<m; i++){
		cin >> d[i] >> s[i] >> t[i];
	} 
	if(check(m)){
		cout << "0";
		return 0;
	}
	while(l<r){
		mid=(l+r)/2;
		if(check(mid)){
			l=mid+1;
		}
		else{
			r=mid;
		}
	}
	cout << "-1\n" << r;
	return 0;
//	for(int i=1; i<=m; i++){
//		if(!check(i))
//		{
//			cout << "-1" << endl << i;
//			return 0;
//		}
//	} 
}

P1843 奶牛晒衣服

数据太水,暴力能过,暴力AC代码

#include <bits/stdc++.h>
using namespace std;
int n, w[500010];
long long l=1, r, mid;
double a, b;
//检验在x秒内, 能否烘干所有的衣服 
bool check(long long x)
{
	long long need=0; 
	//枚举n件衣服 
	for(int i=1; i<=n; ++i){
		//如果x秒内, 衣服可以自然晒干, 则不需要占用烘干机 
		if(x*a>=w[i]){
			continue; 
		}
		else{	//需要占用烘干机 
			need+=ceil((w[i]-x*a)/b);
			if(need>x){
				return false;
			}
		}
	}
	return true;
}
int main()
{
	//a表示一秒自然晒干的湿度 
	//b表示一秒额外烘干的湿度 
	scanf("%d %lf %lf", &n, &a, &b);
	//每件衣服的初始湿度 
	for(int i=1; i<=n; ++i){
		scanf("%d", &w[i]);
		r+=w[i];
	}
	for(long long i=1; i<=r; ++i){
		if(check(i)){
			printf("%lld", i);
			return 0;
		} 
	} 
	return 0;
}

二分AC代码

#include <bits/stdc++.h>
using namespace std;
int n, w[500010];
long long l=1, r, mid;
double a, b;
//检验在x秒内, 能否烘干所有的衣服 
bool check(long long x)
{
	long long need=0; 
	//枚举n件衣服 
	for(int i=1; i<=n; ++i){
		//如果x秒内, 衣服可以自然晒干, 则不需要占用烘干机 
		if(x*a>=w[i]){
			continue; 
		}
		else{	//需要占用烘干机 
			need+=ceil((w[i]-x*a)/b);
			if(need>x){
				return false;
			}
		}
	}
	return true;
}
int main()
{
	//a表示一秒自然晒干的湿度 
	//b表示一秒额外烘干的湿度 
	scanf("%d %lf %lf", &n, &a, &b);
	//每件衣服的初始湿度 
	for(int i=1; i<=n; ++i){
		scanf("%d", &w[i]);
		r+=w[i];
	}
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		} 
		else{
			l=mid+1;
		}
	} 
	printf("%d", r);
	return 0;
}

P1281 书的复制

#include <bits/stdc++.h>
using namespace std;
int m, k, a[510], l, r, need, ans[510][3];
//判断k个人x秒能否全部抄完m本数 
//转换为x秒抄完m本数用的人数是否小于等于k 
bool check(int x)
{
	need=1;
	int cur=0;
	ans[need][1]=m;
	for(int i=m; i>=1; --i){
		if(a[i]>x)	return false;
		if(cur+a[i]<x){	//第need个人x秒内能抄完第i本书 
			cur+=a[i];
			continue;
		}
		else if(cur+a[i]>x){ 	//第i本书必须得下一个人来完成 
			ans[need][2]=i+1;
			need++; 
			cur=a[i];
			ans[need][1]=i; 
		}
		else if(cur+a[i]==x){	//第need个人在x秒内刚好能抄完第i本书 
			ans[need][2]=i;
			cur=0;
			if(i>1){
				need++;
				ans[need][1]=i-1;
			}	
		}
		if(need>k){
			return false;
		}
	}
	if(cur>0){
		ans[need][2]=1;
	}
	return true;
} 
int main()
{
	scanf("%d %d", &m, &k);		//m本书、k个人去抄写
	for(int i=1; i<=m; ++i){
		scanf("%d", &a[i]);		//每本书的页数 
		l=max(l, a[i]);
		r+=a[i];
	} 
	
	for(int i=l; i<=r; ++i){
		if(check(i)){	//如果i秒能抄完
			for(int j=need; j>=1; --j){
				printf("%d %d\n", ans[j][2], ans[j][1]);
			} 
			return 0;
		}
	} 
	return 0;
}

提供1个测试点

P1281_1.in

1 1
1

P1281_1.out

1 1

二分代码

#include <bits/stdc++.h>
using namespace std;
int m, k, a[510], l, r, mid, need, ans[510][3];
//判断k个人x秒能否全部抄完m本数 
//转换为x秒抄完m本数用的人数是否小于等于k 
bool check(int x)
{
	need=1;
	int cur=0;
	ans[need][1]=m;
	for(int i=m; i>=1; --i){
		if(a[i]>x)	return false;
		if(cur+a[i]<x){	//第need个人x秒内能抄完第i本书 
			cur+=a[i];
			continue;
		}
		else if(cur+a[i]>x){ 	//第i本书必须得下一个人来完成 
			ans[need][2]=i+1;
			need++; 
			cur=a[i];
			ans[need][1]=i; 
		}
		else if(cur+a[i]==x){	//第need个人在x秒内刚好能抄完第i本书 
			ans[need][2]=i;
			cur=0;
			if(i>1){
				need++;
				ans[need][1]=i-1;
			}	
		}
		if(need>k){
			return false;
		}
	}
	if(cur>0){
		ans[need][2]=1;
	}
	return true;
} 
int main()
{
	scanf("%d %d", &m, &k);		//m本书、k个人去抄写
	for(int i=1; i<=m; ++i){
		scanf("%d", &a[i]);		//每本书的页数 
		l=max(l, a[i]);
		r+=a[i];
	} 
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid)){	//如果mid秒能抄完
			r=mid;
		}
		else{
			l=mid+1;
		}
	} 
	check(l);
	for(int j=need; j>=1; --j){
		printf("%d %d\n", ans[j][2], ans[j][1]);
	} 
	return 0;
}

P1883 函数

暴力做法 超时,0分

#include <bits/stdc++.h>
using namespace std;
int t, n, a[10010], b[10010], c[10010];
double l, r, mid, ans, eps=1e-5;
//x取某一个值时, 得到的F(x) 
double cal(double x)
{
	double mx=-1e7, sum;
	for(int i=1; i<=n; ++i){
		sum=a[i]*x*x+b[i]*x+c[i];
		mx=max(mx, sum);
	}
	return mx;
}
int main()
{
	scanf("%d", &t);	
	while(t--){		//t<10
		scanf("%d", &n);	//n<=10^4
		ans=1e9;
		for(int i=1; i<=n; ++i){
			scanf("%d %d %d", &a[i], &b[i], &c[i]);
		}
		for(double i=0; i<=1000; i+=eps){
			ans=min(ans, cal(i));
		}
		printf("%.4lf\n", ans);
	}
	return 0;
}

三分做法,本题单峰函数

#include <bits/stdc++.h>
using namespace std;
int t, n, a[10010], b[10010], c[10010];
double l, r, mid1, mid2, eps=1e-9;
//x取某一个值时, 得到的F(x) 
double cal(double x)
{
	double mx=-1e7, sum;
	for(int i=1; i<=n; ++i){	//n<=10^4 
		sum=a[i]*x*x+b[i]*x+c[i];
		mx=max(mx, sum);
	}
	return mx;
}
int main()
{
	scanf("%d", &t);	
	while(t--){		//t<10
		scanf("%d", &n);	//n<=10^4
		for(int i=1; i<=n; ++i){
			scanf("%d %d %d", &a[i], &b[i], &c[i]);
		}
		l=0;    //记得每次都得赋值为0
		r=1000;
		//三分可能的区间 
		while(l+eps<r){		//log(10^15)<50 
			mid1=(2*l+r)/3;	//三等分点 	O(n)		10^4
			mid2=(l+2*r)/3;	//三等分点
			if(cal(mid1)<cal(mid2)){	//题目要求最小值 
				//说明答案偏向在左部
				r=mid2-eps; 
			} 
			else{	//答案偏向在右部 
				l=mid1; 
			}
		}
		printf("%.4lf\n", cal(l));
	}
	return 0;
}

P4343 [SHOI2015]自动刷题机

#include <bits/stdc++.h>
using namespace std;
int n, k, a[100010];	
long long l=1, r=1e14, mid;
bool asd;
//达到m行就可以提交AC一题 
long long check(long long m)
{
	long long acnum=0, cur=0;
	for(int i=1; i<=n; ++i){
		cur+=a[i];
		if(cur<0){
			cur=0;
		}
		else if(cur>=m){
			cur=0;
			acnum++;
		}
	} 
	return acnum;
}
int main()
{
	scanf("%d %d", &n, &k);
	for(int i=1; i<=n; ++i){	//n条日志 
		scanf("%d", &a[i]);		//每条日志写的代码数 
	}
	while(l<r){
		mid=(l+r)>>1;
		//求最小值
		//如果恰好能切了k道题, 尽量往小 
		//如果切的题小于等于k, 说明要求高了, 尽量往小了走 
		if(check(mid)<=k){		 
			r=mid;
		}
		else if(check(mid>k)){	//如果切的题大于k, 说明要求低了, 往大了走 
			l=mid+1; 
		}
	} 
	if(check(l)==k){
		printf("%lld ", l);
		asd=true;
	}
	l=1, r=1e14;
	while(l<r){
		mid=(l+r+1)>>1;
		//求最大值
		//如果恰好能切了k道题, 尽量往大 
		//如果切的题大于等于k, 说明要求低了, 往大了走 
		if(check(mid)>=k){		 
			l=mid;
		}
		else if(check(mid<k)){	//如果切的题小于k, 说明要求高了, 往小了走
			r=mid-1; 
		}
	} 
	if(check(l)==k){
		printf("%lld", l);
		asd=true;
	}
	if(!asd){
		puts("-1");
	}
	return 0;
}

P1570 KC 喝咖啡

P1661 扩散

P1258 小车问题

P6069 『MdOI R1』Group

P4058 [Code+#1]木材

P2920 [USACO08NOV]Time Management S

P2855 [USACO06DEC]River Hopscotch S

P2804 神秘数字

P2390 地标访问

P2005 A/B Problem II

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
二分查找二分答案是两个不同的概念。 二分查找(Binary Search)是一种常用的查找算法,适用于有序的数组或列表。它通过将目标值与数组中间元素进行比较,从而确定目标值在数组的左半部分还是右半部分,然后再在相应的半部分中继续查找,直到找到目标值或者确定目标值不存在。二分查找的时间复杂度是 O(logn)。 以下是一个简单的 C++ 实现示例: ```cpp int binarySearch(int arr[], int target, int left, int right) { while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; else if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; // 目标值不存在 } ``` 二分答案(Binary Search for Answer)是一种在某个范围内寻找满足某个条件的最优解的方法。它通过在一个给定范围内进行二分搜索,每次确定一个中间值,然后判断该中间值是否满足条件。如果满足条件,则将搜索范围缩小到左半部分;如果不满足条件,则将搜索范围缩小到右半部分。通过不断缩小搜索范围,最终找到满足条件的最优解。 这种方法在一些问题中很常见,比如在一个有序数组中找到满足某个条件的最小/最大值,或者确定一个函数的最大/最小值等。 具体的实现方式会根据具体的问题而有所不同,但基本的思路是相似的。你可以根据具体的问题来实现相应的二分答案算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ypeijasd

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

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

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

打赏作者

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

抵扣说明:

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

余额充值