二分其实就是一种效率较高的枚举,在知道答案范围的情况下,不断缩小范围,把时间复杂度下降
适用于求最大的最小,最小的最大
// 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;
}