二分查找与二分答案

二分其实就是一种效率较高的枚举,在知道答案范围的情况下,不断缩小范围,把时间复杂度下降
适用于求最大的最小,最小的最大

// target == key
// 找到目标值或者构不成有效区间就返回
int binarySearch( vector<int>& nums, int target)
{
    int letft = 0;
    int right = nums.size()-1;//查找区间范围 [left,right]
    while(left <= right)// 结束条件 left = right+1 时,[right+1,right] 构不成合理区间范围
    {
        int mid = left + (right - left )/2;//防止相加后超 int 范围
        if( nums[mid] == target )//找到了
        {
            return mid;
        }
        else if( nums[mid] < target )// mid 值小,需要增加左边界,且 mid 位置已排除
        {
            left = mid +1;
        }
        else if( nums[mid] > target )// mid 值大,需要缩小右边界,且 mid 位置已排除
        {
            right = mid -1;
        }
        return -1//没有找到目标值
    }
}

例如:二分法求函数的解

//x^3-5x^2+10x-80=0的根
  //求导发现是单增 ,f0<0 f100>0 
  double EPS=le-6;
  double f(double x){
  	return x*x*x-5*x*x+10*x-80;
  }
  int main()
  {
  	double root,x1=0,x2=100,y;
  	root=x1+(x2-x1)/2;//防止溢出的好方法
  	int triedTimes=1;
  	y=f(root);
  	while(fabs(y)>ESP)//绝对值 (期待变0) 这里基本上是和0比较,毕竟是求根
  	{
  		if(y>0) x2=root;//右端点改成了root 
  		else x1=root;
  		root=x1+(x2-x1)/2;
  		y=f(root);
  		triedTimes++;
	  }
	  printf("%.8f\n",root); 
  }

P2249 查找
使用二分法一定要先排序,否则是没有意义的

#include<iostream>
using namespace std;
int a[19393831],b;
int find(int x,int left,int right)
{
	int mid; 
	mid=(left+right)/2;
	while(left<=right)
	{if(x<mid)
	{
		find(x,left,mid);
	}
	if(x>mid)
	{
	   find(x,mid,right);
	}
	if(x==mid)
	{
		return mid;
	}
	 } 
	 return -1;	
}

int main()
{
	int m,n;
	cin>>m>>n;
	for(int i=1;i<=m;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		cin>>b;
		cout<<find(b,a[1],a[m])<<" ";
	}
	return 0;
}
//#include<iostream>
//using namespace std;
//int m,n,p,ans,a[1038388];
//int find(int x)
//{
//	int l=1,r=n;
//	while(l<r)
//	{
//		int mid=l+(r-l)/2;
//		if(x<=a[mid])
//		r=mid;
//		else
//		l=mid+1;
//	}
//	if(a[l]==x)
//	return l;
//	else return -1;
//	}
//int main()
//{
//	cin>>n>>m;
//	for(int i=1;i<=n;i++)
//	{
//		cin>>a[i];
//	}
//	for(int i=1;i<=m;i++)
//	{
//		cin>>p;
//		cout<<find(p)<<" ";
//	}
//	return 0;
//}
//
//
//
#include <iostream>
#include <vector>
using namespace std;
vector<int > vec;
int main() {
	int n, m;
	int t;
	cin >> n >> m;
	int index;
	while(n--) cin >> t, vec.push_back(t);
	while(m--) {
		cin >> t;
		index = lower_bound(vec.begin(), vec.end(), t) - vec.begin();
  		if (t == vec[index]) cout << index + 1 << ' ';
        
	else cout << -1 << ' ';
	}
	return 0;
}

这里有一些非常好用的stl
提供在已经排好序的数组上进行二分查找

binary_search(数组名+n1,数组名+n2,值)
T*=lower_bound(数组名+n1,数组名+n2,值)大于等于
  =upper_bound 大于
int main()
 {
 	int a[NUM]={12,5,3,5,98,21,7}
 	sort(a,a+NUM);
 	Print(a,NUM);//3,5,5,7,12,21,98
 	int *p=lower_bound(a,a+NUM,5);
 	cout<<*p<<p-a<<endl;//5,1    p-a是指p指向元素的下标 
 	p=upper_bound(a,a+NUM,5);
 	cout<<*p<<endl;//7
 	cout<<*upper_bound(a,a+NUM,13)<<endl;//21
 	
 	sort(a,a+NUM,Rule());
    Print(a,NUM)//21,12,3,5,5,7,98
	cout<<*lower_bound(a,a+NUM,16,Rule());//个位数比较 ,下标最小,大于6   7 
	cout<<lower_bound(a,a+NUM,25,Rule())-a;//>=5 3
	cout<<upper_bound(a,a+NUM,18,Rule())-a;//>8   找不到,区间终点NUM  7 
	if(upper_bound(a,a+NUM,18,Rule())==a+NUM)
	  cout<<"not found";
	cout<<*upper_bound(a,a+NUM,5,Rule())<<endl;//7
	cout<<*upper_bound(a,a+NUM,4,Rule())<<endl;//5
	return 0;	  
 }

p1102
可以用map的一一映射

    #include <iostream>
    #include <map>
    using namespace std;
    typedef long long LL;
    LL a[200001];
    map<LL,LL> m;//建立一个数字到出现次数的映射 map<num,times>
    //A-B=C --> A-C=B
    int main() {
        int n;
        LL c;
        LL ans=0;
        cin >> n >> c;
        for(int i=1;i<=n;i++) {
            cin >> a[i];
            m[a[i]]++;
            a[i]-=c;    
        } 
        for(int i=1;i<=n;i++) ans+=m[a[i]];
        cout << ans << endl;
        return 0;
}

map:第一个是关键字,第二个是数值,可以通过第一个映射到第二个,可以更改第二个内容 m[aa]++就是其中的second增加了,关键字更改只要他名称不变就还是映射关系

例:统计大量英文中单词出现排序

#include<iostream>
 #include<map>
 #include<string>
 using  namespace std;
 struct Word
 {
 	int times;
 	string wd;
 	
 };
 struct Rule{
 	bool operator()(const Word &w1,const Word&w2)const{
	 if(w1.times!=w2.times)
	 return w1.times>w2.times;
	 else
	 return w1.wd<w2.wd;
 };
 int main()
 {
 	string s;
 	set<Word,Rule>st;///放word按照rule排序 
 	map<string,int>mp;
 	while(cin>>s)//读入所有单词 
 	   ++mp[s];/mp[]是在找s为关键字的元素 ,有的话就会返回second,++second就是把次数增加了;没有的话就会建立一个 
 	   for(map<string,int>::iterator i=mp.begin();i!=mp.end() ;++i)///搞了一个迭代器 ,把map搞到set里面 
 	   {
 	   	Word tmp;
 	   	tmp.wd=i->first;//i中取出一个元素放到tmp里面 
 	   	tmp.times=i->second;
 	   	st.insert(tmp);//tmp插入set 
		}
		for(set<Word,Rule>::iterator i=st.begin();i!=st.end();++i)
		cout<<i->wd<<" "<<i->times<<endl;
}


p1024

#include<iostream>
using namespace std;
double l,r,x1,x2,s,m,a,b,c,d; 
double f(double x)
{
	return x*x*x*a+b*x*x+c*x+d; 
}
int main()
{   cin>>a>>b>>c>>d;
	for(int i=-100;i<100;i++)
	{
       l=i;
	   r=i+1;
	   x1=f(l);
	   x2=f(r);
	   if(!f(l))
	   {
	   	printf("%.2f ",l);
	   	s++;
		   }	
		if(x1*x2<0)
		{
			while((r-l)>=0.001)
			{
				m=(l+r)/2;
				if(f(m)*f(r)<0)
				l=m;
				else
				r=m;
			}
			printf("%.2f ",r);
			s++;
		}
		if(s==3)
		break;
	}
	return 0;
}
#include<iostream>
#include<cstdio>
using namespace std;
double a,b,c,d,a1,b1,c1,d1;// 题目要的数据是小数点后2位所以定义首先用double
int num;// num用来记录解的个数 因为一元三次方程只有三个解  解达到三个以后就break掉 减少多余循环
int main()
{
	scanf("%lf%lf%lf%lf",&a,&b,&c,&d);// double类型用 lf 输入哦
	for(double i=-100.00;i<=100.00;i+=0.001)// 最后结果保存两位数 所以这里i每次加0.001(n只有100所以暴不了)
	{
		double l=i,r=i+0.001;
		if((a*l*l*l+b*l*l+c*l+d)*(a*r*r*r+b*r*r+c*r+d)<0)// 若存在两个数x1,x2且x1<x2,f(x1)*f(x2)<0 则方程解肯定在x1~x2范围内   基本数学原理
		printf("%.2f ",l),num++;// 小数点后两位输出
		if(num==3) break;// 解达到三个break掉
	}
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
double a,b,c,d;
int main(){
	scanf("%lf%lf%lf%lf",&a,&b,&c,&d); // 输入
	for(double i=-100;i<=100;i+=0.001){//枚举每个答案
		if(fabs(i*i*i*a+i*i*b+i*c+d)<0.0001)//避免double精度错误
			printf("%.02lf ",i);//两位小数输出
	}
	return 0;
}

其实想来就暴力枚举,从-100到100实验,每一次l,r都相差很小,直到两者异号,l每次增加0.01

更暴力一点 直接带入求值,反正挂不了

emm好像此时用1来二分就有点愚蠢,但想来也是有一定道理的,这个就是大面积确定之后,然后两者之间在不断取mid

p1678
首先再次反思二分在什么时候用
再有大量的数据,并且可以排序,往往用stl
p2440

#include<iostream>
using namespace std;
int m,n,a[1009000],cnt;
long long int r,mid,l;
int main()
{
	cin>>m>>n;
	for(int i=1;i<=m;i++)
	{
		cin>>a[i];
	}
	l=0;r=1e8+1;
	while(l+1<r)
	{
		mid=(l+r)/2;
		cnt=0;
		for(int i=1;i<=m;i++)
		{
			cnt+=a[i]/mid;
		}
		if(cnt>=n)
		l=mid;
		if(cnt<n)
		r=mid;
		
	}
	cout<<l;
	return 0;
}

好,终于迎来了二分的高级算法
这种往往是有奇怪的分割,然后要求最大的最小或者是最小的最大
这种往往一开始是没有范围的,所以你需要一个极大的数,一个极小的数,
然后最重要的是如何去判断你这个,往往要利用到一个总的没有利用的数字·,比如这里的k
如果你这个小木段小的很,那么就会大于k,大了就会小

P2678

#include<iostream>
using namespace std;
int L,N,M;
int a[1000000];  
bool judge(int x)
{
	int num=0,now=0,i=0;
	while(i<N+1)
	{
		i++;
		if(a[i]-a[now]<x)
		{
			num++;
		}
		else
		now=i;
		
	}
	if(num>M) return 0;
	else return 1;
}
int main()
{
	int ans;
	cin>>L>>N>>M;
	for(int i=1;i<=N;i++)
	{
		cin>>a[i];
	}
	a[N+1]=L; //不要忘记还有最后一个空间要跳 
	int r=L;
	int l=1,mid;
	while(l<=r)
	{
	  mid=(l+r)/2;
	  if(judge(mid))
	  {
	  	ans=mid;
	  	l=mid+1;
	  }
	  else r=mid-1;
	  
	}
	cout<<ans;
	 
}

他是个最短跳跃距离,所以理论上,他就不应该跳的过去,所以一旦跳过去的那么就是要被搬走的,但他也必须要跳过去,所以这个搬走的数量有限(找搬走后最短的jump)尝试,如果你搬错了,那么你后面肯定遇到更小的又要搬,直到exactly

p1824

定义变量ans,储存当前优解。定义闭区间[left, right],代表程序当前正在此闭区间内寻找答案(寻找潜在的比ans更优的解)(与前两种方法不同的是,最优解不一定要属于该闭区间)。

令mid = (left + right)/2

若mid为解,则ans = max(ans, mid), left = mid + 1.此时我们更新了最优解,同时在最优解的右侧寻找潜在的更有解。

若mid不为解,则right = mid - 1.mid不是解,因此我们在mid左边寻找更优解。

重复上述过程,直到left > right时跳出循环,ans即为最优解。

注意,当left == right时,也必须要在此区间内进行判断,因为当前还不能确定该区间内是否存在更优解。
while(left <= right)
{
    int mid = (left + right) / 2;
    if(judge(mid))
    {
        left = mid + 1;
        ans = max(ans, mid);
    }
    else
        right = mid - 1;
}
printf("%d", ans);

这种思想好像确实更适合做这种最大的最小的问题提,因为真的不一定

P1182

#include<iostream>
using namespace std;
long long int  cc,n,m,l,mid,r,a[10000000],cnt;
int check (int p)
{
	int cc ;
	for(int i=1;i<=n;i++)
	{
//		cc+=a[i];
//		if(cc>=p)
//		{
//			cnt++;
//		#include<iostream>
using namespace std;
long long int  cc,n,m,l,mid,r,a[10000000],cnt;
int check (int p)
{
	int cc ;
	for(int i=1;i<=n;i++)
	{
//		cc+=a[i];
//		if(cc>=p)
//		{
//			cnt++;
//			cc=0;
//		}
       if(a[i]+cc<=p)
       cc+=a[i];
       else
       {
       	cnt++;
       	cc=0;
	   }
	}
	if(cnt>m) return 1;
	else return 0;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		l=max(l,a[i]);
		r+=a[i];
	}
	while(l+1<r)
	{
		mid=l+(r-l)/2;
		if(check(mid))l=mid-1;
		else r=mid+1;
	}
	cout<<l;
}	cc=0;
//		}
       if(a[i]+cc<=p)
       cc+=a[i];
       else
       {
       	cnt++;
       	cc=0;
	   }
	}
	if(cnt>m) return 1;
	else return 0;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		l=max(l,a[i]);
		r+=a[i];
	}
	while(l+1<r)
	{
		mid=l+(r-l)/2;
		if(check(mid))l=mid-1;
		else r=mid+1;
	}
	cout<<l;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值