二分,三分入门例题

A- 查找

题目大意:输入 n 个不超过 10^9 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1​,a2​,…,an​,然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。
解题思路:由于本题输出量较大,需要使用较快的二分算法。

#include<stdio.h>
int a[1000010];
int m,n;
void work(int b)
{
	int l=0;
	int r=n-1;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(a[mid]>=b)
		{
			r=mid;
		}
		else
		{
			l=mid+1;
		}
	}
	if(b==a[l])
	{
		printf("%d ",l+1);
	}
	else
	{
		printf("-1 ");
	}
}
int main()
{
	int i,b;
	scanf("%d %d",&n,&m);
	for(i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(i=0;i<m;i++)
	{
		scanf("%d",&b);
		work(b);
	}
	return 0;
 } 

B- A-B数对

题目大意:给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数。
解题思路:由于本题的n小于2e5,数据量较大,需要通过二分查找简化算法,通过遍历由第一个a[i]为A,算出B,通过二分查找找到对应的B的最左端和最右端,得到B的个数。

#include<stdio.h>
#include<algorithm>
using namespace std;
long long a[200010];
long long n;
long long work(long long b)
{
	long long l1=0,r1=n-1,mid1;
	while(l1<r1)
	{
		mid1=l1+r1>>1;
		if(a[mid1]>=b)
		{
			r1=mid1;
		}
		else
		{
			l1=mid1+1;
		}
	}
	if(a[l1]!=b) return 0; 
	long long l2=l1-1,r2=n-1,mid2; 
	while(l2<r2)
	{
		mid2=l2+r2+1>>1;
		if(a[mid2]<=b)
		{
			l2=mid2;
		}
		else
		{
			r2=mid2-1;
		}
	}
	return l2-l1+1;
}
int main()
{
	long long c,sum=0;
	int i;
	scanf("%lld %lld",&n,&c);
	for(i=0;i<n;i++)
	{
		scanf("%lld",&a[i]);
	}
	sort(a,a+n);
	for(i=0;i<n;i++)
	{
		if(a[i]>c)
		{
			sum+=work(a[i]-c);
		}
	}
	printf("%lld",sum);
	return 0;
} 

C- 分巧克力

题目大意:小明一共有 N 块巧克力,其中第 i 块是 Hi​×Wi​ 的方格组成的长方形。小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:1. 形状是正方形,边长是整数。2. 大小相同。
解题思路:在输入的时候找到面积最大的一块巧克力记为max,以最大的这块巧克力的边长作为right,以0作为left,根据二分一步一步缩小范围,若根据mid分出来的巧克力块数多了说明mid小了,巧克力块数少了说明mid大了。

#include<stdio.h>
int n,k;
struct move
{
	int start,end;
}a[100010];
int check(int mid)
{
	int i,sum=0;
	for(i=0;i<n;i++)
	{
		sum+=(a[i].end /mid)*(a[i].start /mid); 
	}
	return sum;
}
int work(int l,int r)
{
	while(l<r)
	{
		int mid=l+r+1>>1;
		if(check(mid)>=k)
		{
			l=mid;
		}
		else
		{
			r=mid-1;
		}
	}
	return l;
}
int main()
{
	int maxs=0,maxe=0,i,j;
	scanf("%d %d",&n,&k);
	for(i=0;i<n;i++)
	{
		scanf("%d %d",&a[i].start ,&a[i].end );
		if(maxs<a[i].start )
		{
			maxs=a[i].start ;
		}
		if(maxe<a[i].end )
		{
			maxe=a[i].end ;
		}
	}
	int l=1,r;
	if(maxs>maxe)
	{
		r=maxs;
	}
	else
	{
		r=maxe;
	}
	printf("%d",work(l,r));
	return 0;
}

D- 跳石头

题目大意:组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
解题思路:以终点和起点之间的距离为right,以0为left,根据二分一步一步缩小范围,在check函数中若搬走的石头大于题目中可以搬走的石头数量,则说明mid大了,若搬走的石头数量小了,说明mid小了。

#include<stdio.h>
int a[50001];
int l,n,m;
int tiao(int left,int right)
{
	if(right-1==left) return left;
	int mid=(left+right)/2;
	int num=0;
	int flag=0;
	int k=0;//k记录当前石头的前一块石头距离岸边的距离 
	for(int i=1;i<=n+1;i++)
	{
		int dis=a[i]-k;
		if(dis>=mid)//如果长度小于等于mid,距离合适,不搬石头 
		{
			k=a[i]; 
		}
		else//石头要被移走 
		{
			num++;
			if(num>m)
			{
				flag=1;
				break;
			}
		}
	}
	if(flag)
	{
		return tiao(left,mid);
	}
	return tiao(mid,right);
}
int main()
{
	int i;
	scanf("%d %d %d",&l,&n,&m);
	a[0]=0;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	a[n+1]=l;
	printf("%d",tiao(1,l+1));//表示[left,right)找到最短距离最大的区间 
	
	return 0;
 } 

E- Cable master

题目大意:光头强需要对木头进行切割,并把长度相同的K根木头送到公司(本题当中的给出的木头长度单位是米,光头强的切割技术只能精确到厘米,也就是说答案保留到小数点后两位小数)。
解题思路:这道题是有小数的二分,首先得到最大的木头的长度作为right,以0作为left,得到的mid通过check函数检查,若切的块数大于李老板要求的块数,则说明mid设小了,若切的块数小于李老板要求的块数,说明mid大了。这道题我wa了特别多次,一直不懂为什么,明明答案是对的,但是一直wa,最后没办法找了同学帮忙看,才知道是被卡精度了,但是还是觉得离谱,我的精度已经卡到1e-7了还是过不了,明明题目只要求保留两位小数,最后知道了这种题直接给它循环来个一百次就好了。

#include<stdio.h>
#include<math.h>
double a[10010];
double n,k;
int check(double mid)
{
	int sum=0,i;
	for(i=0;i<n;i++)
	{
		sum+=(int)(a[i]/mid); 
	}
	return sum; 
}
void work(double l,double r)
{
	int i;
	for(i=0;i<100;i++)
	{
		double mid=(l+r)/2;
		if(check(mid)>=k)//分的木头多了,要增大单块木头的长度 
		{
			l=mid;
		}
		else
		{
			r=mid;
		}
	}
	printf("%.2f\n",floor(l*100)/100);
}
int main()
{
	int i;
	double max=0,sum=0;
	scanf("%lf %lf",&n,&k);
	for(i=0;i<n;i++)
	{
		scanf("%lf",&a[i]);
		sum+=a[i];
		if(max<a[i])
		{
			max=a[i];
		}
	}
	sum/=n;
	double l=0,r;
	if(sum>max)
	{
		r=sum;
	}
	else
	{
		r=max;
	}
	work(l,r);
	return 0;
}

F- Yukari’s Birthday

题目大意:给蛋糕一圈一圈插上蜡烛,第i圈为k的i次方,相当于再求一个关于k的i次方的等比数列的前n项和。
解题思路:枚举+二分。枚举r,然后再二分得出k的大小。在做这题的时候一直wa是因为没有考虑到乘法溢出,需要判断乘法会不会溢出。

#include<stdio.h>
#include<math.h>
int n;
int check(int mid,int r)
{
	int sum=0,i;
	for(i=1;i<=r;i++)
	{
		sum+=pow(mid,i);
	}
	return sum;
}
int work(int r)
{
	int right=0;
	int left=n+1;
	while(right!=left+1)
	{
		int mid=left+right>>1;
		if(check(mid,r)>=n)
		{
			right=mid;
		}
		else{
			left=mid;
		}
	}
	return left;
}
int main()
{
	int r,k;
	int min=0x3f3f3f3f;
	int minr=0x3f3f3f3f;
	int mink=0x3f3f3f3f;
	while(~scanf("%d",&n))
	{
		for(r=1;r<=100;r++)
		{
			k=work(r);
			if(min>k*r)
			{
				mink=k;
				minr=r;
				min=k*r;
			}
			else if(min=k*r)
			{
				if(minr>r)
				{
					minr=r;
					mink=k;
				} 
			}
		}
		printf("%d %d",minr,mink);
	}
	return 0;
}

G- Pie

题目大意:有n个不同大小的蛋糕,要求分成k份,所有人拿到的蛋糕是同样大小的 (但不必是同样形状的)
解题思路:在输入的时候输入的是半径,我们要把它处理成半径的平方,然后以最大的那块蛋糕作为right,以0作为left,在check函数中,若得到的蛋糕块数大于总人数说明mid小了,若得到的蛋糕块数小于总人数则说明mid大了。

#include<stdio.h>
#include<math.h>
#include<string.h>
int a[10010];
int f,n;
const double pi=acos(-1.0);
int check(double mid)
{
	int sum=0,i;
	for(i=0;i<n;i++)
	{
		sum+=(int)(a[i]/mid);
	}
	return sum;
}
void work(double l,double r)
{
	double mid;
	while(r-l>1e-7)
	{
		mid=(r+l)/2;
		if(check(mid)>=f+1)//蛋糕块数多了 
		{
			l=mid;
		}
		else//蛋糕块数少了 
		{
			r=mid;
		}
	}
	printf("%.4lf\n",mid*pi);
}
int main()
{
	int t,i,j;
	double max;
	scanf("%d",&t);
	while(t--)
	{
		memset(a,0,sizeof(a));
		max=0;
		scanf("%d %d",&n,&f);
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
			a[i]*=a[i];
			if(max<a[i])//取pie的最大值 
			{
				max=a[i];
			}
		}
		double l=0,r=max;
		work(l,r);
	}
	return 0;
} 

H- Monthly Expense

题目大意:给你一个长度为N的序列,现在需要把他们切割成M个子序列(所以每一份都是连续的),使得每个子序列和均不超过某个值X。
解题思路:累加得到这个序列的总数,以这个序列的总数作为right,以0作为left,通过check函数若分成的段数大于题目要求的段数说明mid小了,分的段数小于题目要求说明mid大了。

#include<stdio.h>
int a[100010];
int m,n;
int check(long long mid)
{
	int i,j,count=0;
	long long sum=0;
	for(i=0;i<n;i++)
	{
		if(sum+a[i]>mid)
		{
			if(i!=n-1)
			{
				sum=0;
				i--;
			}
			count++;
		}
		else
		{
			sum+=a[i];
		}
	}
	if(!sum)count++;
	return count;
}
void work(long long l,long long r)
{
	int i;
	while(l<r)
	{
		long long mid=r+l>>1;
		if(check(mid)>=m)//说明选的数字小了 
		{
			l=mid+1;
		}
		else
		{
			r=mid;
		}
	}
	printf("%lld\n",l);
}
int main()
{
	int i;
	while(~scanf("%d %d",&n,&m))
	{
		long long sum=0,max=0;
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
			sum+=a[i];
			if(max<a[i])
			{
				max=a[i];
			}
		}
		long long l=max,r=sum;
		work(l,r);
	}
	return 0;
}

I- 三分法

题目大意:给出一个 N 次函数,保证在范围 [l, r] 内存在一点 x,使得 [l, x] 上单调增,[x, r] 上单调减。试求出 x 的值。
解题思路:该题求得是最大值,需要将区间分成三段:[left,mid1],[mid1,mid2],[mid2,right]。mid1=(left+right)/2,mid2=(right+mid1)/2。然后算的时候看是mid1对应的函数值大,还是mid2对应的函数值大,如果是mid1的函数值大,那就right=mid2,如果是mid2的函数值大,那就left=mid1。

#include<stdio.h>
#include<math.h>
int n;
double a[20];
double check(double mid)
{
	double sum=0;
	int i;
	for(i=0;i<=n;i++)
	{
		sum+=a[i]*pow(mid,n-i);
	}
	return sum;
}
void work(double l,double r)
{
	while(r-l>1e-8)
	{
		double mid1=(l+r)/2;
		double mid2=(mid1+r)/2;
		if(check(mid1)>check(mid2))
		{
			r=mid2;
		}
		else
		{
			l=mid1;
		}
	}
	printf("%.5lf",l);
}
int main()
{
	int i;
	double l,r;
	scanf("%d %lf %lf",&n,&l,&r);
	for(i=0;i<=n;i++)
	{
		scanf("%lf",&a[i]);
	}
	work(l,r);
	return 0;
}

J- Freefall

题目大意
高桥的行星有一个常数 g 表示重力的强度,他开始下落后到达地面的时间是A/sqrt(g)。现在时间是0,g = 1。高桥将执行以下操作多次,只要他想(可能零)。使用超能力将 g 的值增加1。这需要一个 B 的时间,然后,他会跳楼。在开始下降后,他不能改变 g 的值,此外,我们只考虑执行操作和下降所需的时间。找到高桥能到达地面的最早时间。
解题思路:这道题相当于构造一个函数为y=b*x+a/sqrt(x+1)​利用三分查找(整数)找到答案,由于本题数据较大,最大的需要注意的点是要看好数据范围,所有数据不是long long就是double,我因为数据范围wa了两次。

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
long long a,b;
double check(double m)
{
	double sum;
	sum=1.0*m*b+1.0*a/sqrt(m+1.0);
	return sum;
}
void work(long long l,long long r)
{
	long long i;
	while(r-l>2)
	{
		long long mid1=(l+r)/2;
		long long mid2=(mid1+r)/2; 
		if(check(mid1)>check(mid2))
		{
			l=mid1;
		} 
		else
		{
			r=mid2;
		}
	}
	double sum=1e18;
	for(i=l;i<=r;i++)sum=min(sum,check(i));
	printf("%0.8f\n",sum);
}
int main()
{
	long long left,right;
	scanf("%lld %lld",&a,&b);
	left=0;
	right=1e18;
	work(left,right);
	return 0;
}

K- Last Rook

题目大意:二维棋盘,现在,N-1车被放置在棋盘上,以便所有上述条件都得到满足。你要选择一个没有车的广场,并在那个广场上放一辆车。(可以证明,在这种情况下,至少有一个正方形上可以放置车。)但是,你不能直接看到棋盘的哪些方格被车占据。相反,你最多可以用以下方式向法官提出20个问题。
解题思路:通过横向二分缩小区间,二维变一维了,在横向二分缩小区间,这道题对我来说最最最大的困难就是读不懂英文。

#include<iostream>
using namespace std;
int main()
{
	int n,l=1,r,x1,y1,x;
	cin>>n;
	r=n;
	while(l<r)
	{
		int mid=(l+r)/2;
		cout<<"? "<<l<<" "<<mid<<" "<<"1 "<<n<<endl;
		cin>>x;
		if(x==mid-l+1)//说明mid往前到l这段都有赛车 
		{
			l=mid+1;
		}
		else
		{
			r=mid;
		}
	}
	x1=l;
	l=1;
	r=n;
	while(l<r)
	{
		int mid=(l+r)/2;
		cout<<"? "<<"1 "<<n<<" "<<l<<" "<<mid<<endl;
		cin>>x;
		if(x==mid-l+1)
		{
			l=mid+1;
		}
		else
		{
			r=mid;
		}
	}
	cout<<"! "<<x1<<" "<<r<<endl;
	return 0;
 } 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值