简单整理一下二分算法相关的基础内容,便于以后参考复习
二分模板
(1)在一组升序数列中找到第一个 ≥ x ≥x ≥x 的位置
int find(int x){
int l=1,r=n,ans-1;
while(l<=r){
int mid=(l+r)>>1;
if(a[mid]>=x){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
return ans;
}
(2)在一组升序数列中找到 x x x 最后一次出现的位置
int find(int x){
int l=1,r=n,ans-1;
while(l<=r){
int mid=(l+r)>>1;
if(a[mid]==x){
ans=mid;
l=mid+1;
}
else if(a[mid]>x) r=mid-1;
else l=mid+1;
}
return ans;
}
(3)查找升序数组中第一个大于或等于 x x x 的位置
lower_bound(a,a+n,x)-a;
(4)查找升序数组中第一个大于 x x x 的位置
upper_bound(a,a+n,x)-a;
(5)查找升序数组中是否存在 x x x (返回值为true/false)
binary_search(a,a+n,x);
(6)浮点二分求解方程
以控制精度误差为
1
0
−
5
10^{-5}
10−5为例
const double eps=1e-5;
double find(){
double l=1,r=10;
while(abs(r-l)>eps){
double mid=(l+r)/2;
if(f(mid)*f(r)<=0){
l=mid;
}
else{
r=mid;
}
}
return l;
}
当然,在一些题目中这样书写可能会TLE,所以我们可以自行控制迭代次数,比如上述区间长度从10减小到 1 0 − 5 10^{-5} 10−5,变化幅度为 1 0 6 10^{6} 106, l o g ( 1 0 6 ) ≈ 20 log(10^{6})≈20 log(106)≈20
double find(){
double l=1,r=10;
for(int i=1;i<=20;i++){
double mid=(l+r)/2;
if(f(mid)*f(r)<=0){
l=mid;
}
else{
r=mid;
}
}
return l;
}
基础应用例题
二分查找
A-B PORBLEM(二分搜索STL的简单应用)
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int n,c,l,r,mm,ll,rr,mid,ans1,ans2;
int a[200010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>c;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
c=abs(c);
long long count=0;
for(int i=0;i<n-1;i++){
count+=((upper_bound(a,a+n,a[i]+c)-a)-(lower_bound(a,a+n,a[i]+c)-a));
}
cout<<count<<endl;
return 0;
}
一元三次方程求解(注意对区间边界以及精度的控制)
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cmath>
using namespace std;
double a,b,c,d,l,mid,r,ans1,ans2,ans3,f1,f2,f3;
double f(double x){
return a*pow(x,3)+b*pow(x,2)+c*x+d;
}
int main()
{
scanf("%lf %lf %lf %lf",&a,&b,&c,&d);
int count=0;
for(int i=-100;i<=99;i++){
l=i,r=i+1;
if(f(r)==0){
printf("%.2lf ",r);
count++;
}
if(f(l)*f(r)<0){
while(r-l>0.001){
mid=(r+l)/2;
if(f(mid)*f(r)<=0)
l=mid;
else
r=mid;
}
printf("%.2lf ",l);
count++;
}
if(count>=3)
break;
}
return 0;
}
银行贷款
- 注意公式,设贷款的原值为
n
n
n,每月支付的分期付款金额为
m
m
m,分期付款还清贷款所需的总月数
k
k
k,则
n = m × ∑ i = 1 k 1 ( 1 + x ) i n = m× \sum_{i=1}^k\frac{1}{(1+x)^i} n=m×i=1∑k(1+x)i1
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
double n,m,t,l,r,mid;
bool cal(double x){
return (m*(1.0-pow(1.0/(1.0+x),t))>=n*x);
}
int main()
{
cin>>n>>m>>t;
l=0;r=10;
while(r-l>=0.0001){
mid=(r+l)/2;
if(cal(mid))
l=mid;
else
r=mid;
}
printf("%.1lf",l*100);
return 0;
}
二分答案
一般为求解最小值最大或最大值最小的问题,需要注意的是对边界的处理(最小往前找,最大往后找)。
kotori的设备(贪心+二分)
- 思路:
(1)对于特殊情况的判断:若所有设备单位时间内消耗能量的总数小于 p p p,则这些设备可以无限使用
(2)贪心策略:将所有设备在不充能情况下的使用时间从小到大排序,优先为时间小的设备充能
(3)二分边界
l = l= l= 设备在不充能的情况下使用的最短时间
r = 1 e 10 r=1e10 r=1e10
(4)二分 c h e c k check check
计算所有设备在使用到 m i d mid mid时的充电总时长 c h a r g e t i m e chargetime chargetime,若小于或等于 m i d mid mid 则令 l = m i d l=mid l=mid,否则令 r = m i d r=mid r=mid
ps:注意一个小小bug,sort排序时n必须为int型 - 代码实现
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
using namespace std;
double n,p,l,r,mid;
struct s{
double a;
double b;
double c;
s(){}
s(double aa,double bb,double cc):a(aa),b(bb),c(cc){}
friend bool operator <(const s &s1,const s &s2){
return s1.c<s2.c;
}
}d[100010];
int main()
{
scanf("%lf %lf",&n,&p);
double a,b,c;
double sum=0;
for(int i=0;i<n;i++){
scanf("%lf %lf",&a,&b);
c=b/a;
d[i]=s(a,b,c);
sum+=a;
}
if(p>=sum){
printf("-1\n");
return 0;
}
int N=n;
sort(d,d+N);
l=d[0].c,r=1e10;
int num=60;
while(num--){
mid=(l+r)/2;
double chargetime=0;
for(int i=0;i<n;i++){
if(d[i].c>=mid)
break;
chargetime+=(d[i].a*mid-d[i].b)/p;
}
if(chargetime<=mid) l=mid;
else r=mid;
}
printf("%lf\n",l);
return 0;
}
数组分段 Section II
#include <iostream>
using namespace std;
int n,m,l,r,mid,ans,element;
int sum[100010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
sum[0]=0;
for(int i=1;i<=n;i++){
cin>>element;
l=max(l,element);
sum[i]=sum[i-1]+element;
}
r=sum[n];
while(l<=r){
mid=(l+r)>>1;
int num=1,i=1,j=1;
while(j<=n){
if(sum[j]-sum[i-1]>mid)
i=j,num++;
j++;
}
if(num<=m) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<endl;
return 0;
}
路标设置
#include <iostream>
using namespace std;
int L,n,k,l,mid,r,ans;
int a[100010],d[100010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>L>>n>>k;
l=1,r=1;
int sign;
a[0]=0;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i]=a[i]-a[i-1];
r=max(r,d[i]);
}
while(l<=r){
mid=(l+r)>>1;
int num=0;
for(int i=2;i<=n;i++){
if(d[i]>mid){
num+=d[i]/mid;
if(d[i]%mid==0)
num--;
}
if(num>k)
break;
}
if(num<=k){
ans=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
cout<<ans<<endl;
return 0;
}
烦恼的高考志愿
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int m,n;
long long ans=0;
int a[100010],b[100010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>m>>n;
for(int i=0;i<m;i++)
cin>>a[i];
for(int i=0;i<n;i++)
cin>>b[i];
sort(a,a+m);
for(int i=0;i<n;i++){
int pos=lower_bound(a,a+m,b[i])-a;
if(pos>0){
ans+=min(abs(a[pos]-b[i]),abs(a[pos-1]-b[i]));
}
else
ans+=abs(a[pos]-b[i]);
}
cout<<ans<<endl;
return 0;
}
木材加工
#include <iostream>
#include <algorithm>
using namespace std;
int n,k,l,r,mid,ans;
int a[100010];
int main()
{
cin>>n>>k;
int suma=0;
for(int i=0;i<n;i++){
cin>>a[i];
suma+=a[i];
}
if(suma<k)
cout<<0<<endl;
else{
sort(a,a+n);
l=1,r=suma/k;
ans=0;
while(l<=r){
mid=l+((r-l)>>1);
int count=0;
for(int i=0;i<n;i++){
count+=a[i]/mid;
if(count>=k)
break;
}
if(count>=k){
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
cout<<ans<<endl;
}
return 0;
}
砍树
#include <iostream>
#include <set>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long ll;
ll n,m,trees[1000010],l,r,mid,ans,sum;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
l=r=1;
for(int i=0;i<n;i++){
cin>>trees[i];
r=max(r,trees[i]);
}
while(l<=r){
mid=l+((r-l)>>1);
sum=0;
for(int i=0;i<n;i++){
if(mid<trees[i]){
sum+=trees[i]-mid;
}
}
if(sum>=m) l=mid+1;
else r=mid-1;
}
cout<<l-1<<endl;
return 0;
}
跳石头
#include <iostream>
using namespace std;
int L,N,M,l,r,mid,count,ans,now;
int a[50010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>L>>N>>M;
for(int i=1;i<=N;i++)
cin>>a[i];
a[0]=0;a[N+1]=L;
l=0;r=L;count=ans=0;
while(l<=r){
mid=(r+l)>>1;
count=now=0;
for(int i=1;i<=N;i++){
if(a[i]-a[now]<mid){
count++;
}
else{
now=i;
}
}
if(count>M){
r=mid-1;
}
else{
ans=mid;
l=mid+1;
}
}
cout<<ans<<endl;
return 0;
}