目录
果真自己还是菜啊,二分模板背的比较熟悉了,做二分的题还是笨的摸不着边际。
P2249 【深基13.例1】查找 【标准的整数二分】
https://www.luogu.com.cn/problem/P2249
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e6+5;
int a[N];
int main(void)
{
int n,m; cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
while(m--)
{
int l=0,r=n-1;
int number; cin>>number;
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=number) r=mid;
else l=mid+1;
}
if(a[l]==number) cout<<l+1<<" ";
else cout<<"-1"<<" ";
}
return 0;
}
P1102 A-B 数对 【哈希】
https://www.luogu.com.cn/problem/P1102
#include<cstdio>
#include<iostream>
#include<map>
using namespace std;
const int N=2*1e7+20;
long long int a[N];
map<int,int>mp;//统计每个数的个数
int main(void)
{
int n,c; cin>>n>>c;
for(int i=0;i<n;i++) cin>>a[i],mp[a[i]]++;
long long int ans=0;
for(int i=0;i<n;i++)
{
ans+=mp[a[i]-c];
}
cout<<ans<<endl;
return 0;
}
P1873 砍树 【经典二分】
https://www.luogu.com.cn/problem/P1873
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e6+10;
long long int a[N];
long long int n,m;
bool check(int mid)
{
long long int ans=0;
for(int i=0;i<n;i++)
{
if(a[i]>mid) ans+=a[i]-mid;
}
if(ans>=m) return true;
else return false;
}
int main(void)
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
int l=0,r=1e9;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
P1024 [NOIP2001 提高组] 一元三次方程求解 【有意思 / 浮点数二分】
https://www.luogu.com.cn/problem/P1024
方法一: 暴力
首先零点的左右两边的符号一定是不同的。
#include<cstdio>
#include<iostream>
using namespace std;
double a,b,c,d;
double f(double x)
{
return a*x*x*x+b*x*x+c*x+d;
}
int main(void)
{
cin>>a>>b>>c>>d;
for(double i=-100;i<=100;i=i+0.01)
{
double l=i,r=i+0.01;
if(f(i)*f(r)<0)//有根
{
double mid=(l+r)/2;
printf("%.2lf ",mid);
}
}
return 0;
}
方法二: 标准的二分
提示很重要。
摘自: https://www.luogu.com.cn/problem/solution/P1024?page=6
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
double a,b,c,d;
double f(double x){
return (x*x*x*a+x*x*b+x*c+d); //f(x)表示将枚举的x带入后函数的数值
}
int main(){
cin>>a>>b>>c>>d;
double x1,x2,mid;
for (int x=-100;x<=100;x++){ //暴力枚举所有有可能的根
x1=x,x2=x+1;
if (f(x1)==0) printf ("%.2lf ",x1); //恰好是整数根
else if (f(x1)*f(x2)<0){ //由f(x1)*f(x2)<0很容易 得知真正的根在(x1,x2)之间
while (x2-x1>=0.001){ //要求保留两位小数,为了保证结果正确,计算时精度要大于0.01
mid=(x1+x2)/2; //二分的思想,将实根的范围不断缩短直到小于0.001
if (f(x1)*f(mid)<=0) x2=mid; //mid为中间值,根如果在(x1,mid) x2=mid; 缩短了根的范围
else x1=mid;
} //两根的范围已经符合要求退出循环
printf ("%.2lf ",x1); //输出结果 x1,(保留两位小数)空格别忘了!
}
}
return 0;
}
P1678 烦恼的高考志愿 【二分 / lower_bound 】
https://www.luogu.com.cn/problem/P1678
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[100005];
int b[100005];
long long int sum;
int main(void)
{
int n,m; cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
sort(a,a+n);
for(int i=0;i<m;i++)
{
int *temp=lower_bound(a,a+n,b[i]);//第一个大于等于b[i]的位置
if(a[temp-a]==b[i]) continue;//如果相等那么不用加
if(a[temp-a]!=b[i]&&(temp-a)==0)//如果没有找到比b[i]小的位置
{
sum+=abs(b[i]-a[0]);
continue;
}
if(a[temp-a]!=b[i]&&(temp-a)==n) //如果没有找到比b[i]大的位置
{
sum+=abs(b[i]-a[n-1]);
continue;
}
sum+=min(abs(a[temp-a]-b[i]),abs(a[temp-a-1]-b[i]));//向b[i]两边取较近的数
}
cout<<sum<<endl;
return 0;
}
简化:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[100005];
int b[100005];
long long int sum;
int main(void)
{
int n,m; cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
sort(a,a+n);
for(int i=0;i<m;i++)
{
int *temp=lower_bound(a,a+n,b[i]);//第一个大于等于b[i]的位置
if(a[temp-a]==b[i]) continue;//如果相等那么不用加
if(temp-a==0)//如果没有找到比b[i]小的位置
{
sum+=abs(b[i]-a[0]);
continue;
}
if(temp-a==n) //如果没有找到比b[i]大的位置
{
sum+=abs(b[i]-a[n-1]);
continue;
}
sum+=min(abs(a[temp-a]-b[i]),abs(a[temp-a-1]-b[i]));//向b[i]两边取较近的数
}
cout<<sum<<endl;
return 0;
}
手写二分
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int a[1000005];
int b[1000005];
int m,n;
long long int sum;
int main(void)
{
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 l=0,r=m-1;
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=b[i]) r=mid;
else l=mid+1;
}
if(a[l]==b[i]) continue;
if(b[i]<=a[0]) {
sum+=abs(b[i]-a[0]);
continue;
}
if(b[i]>=a[m-1]){
sum+=abs(b[i]-a[m-1]);
continue;
}
sum+=min(abs(b[i]-a[l]),abs(b[i]-a[l-1]));
}
cout<<sum<<endl;
return 0;
}
P2440 木材加工
https://www.luogu.com.cn/problem/P2440
#include<cstdio>
#include<iostream>
using namespace std;
int a[100005];
int n,k;
bool check(int x)
{
long long int ans=0;
for(int i=0;i<n;i++)
ans+=a[i]/x;
if(ans>=k) return true;
else return false;
}
int main(void)
{
cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
int l=0,r=1e9;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
if(l) cout<<l<<endl;
else cout<<0<<endl;
return 0;
}
P1163 银行贷款 【浮点数二分】
https://www.luogu.com.cn/problem/P1163
#include<cstdio>
#include<iostream>
using namespace std;
int a,b,c;
double check(double x)
{
x=x/100;
double sum=a;
for(int i=1;i<=c;i++)
{
sum=sum*(1+x)-b;
}
return sum;
}
int main(void)
{
cin>>a>>b>>c;
double l=0,r=1000;
while((r-l)>0.0000001)
{
double mid=(l+r)/2;
if(check(mid)*check(l)<=0) r=mid;
else l=mid;
}
printf("%.1lf",l);
return 0;
}
P2678 [NOIP2015 提高组] 跳石头 【非常秒的整数二分】
https://www.luogu.com.cn/problem/P2678
话说,我好想懂得了二分的规律,看问题问的啥来确定边界。
思路摘自: ztc曾天成
我们的mid是最小距离。
思路:从起点出发,先选定一段距离mid,若前面的石头B与你所站着的石头A的距离小于mid(不符合,因为我们的mid应该是最下的),就把B搬掉,记录一下;如果不,就把B留下,再跳到石头B上。照这个步骤多次循环后,如果搬掉的石头多了,
就把距离mid定小点;如果少了,就把mid定大点。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+10;
int a[N];
int sum,n,m;
bool check(int x)
{
int temp=0;
int cnt=0;;
for(int i=0;i<n;i++)
{
if(a[i]-temp<x) cnt++;//拿走
else temp=a[i];//跳到下一个石头
}
if(sum-temp<x) cnt++;//最后到终点的位置
if(cnt<=m) return true;
else return false;//拿的少了
}
int main(void)
{
cin>>sum>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
int l=0,r=1e9;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
P3853 [TJOI2007]路标设置 【有意思】
https://www.luogu.com.cn/problem/P3853
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e7+10;
int sum,n,m;
int a[N];
bool check(int x)
{
int temp=0;
long long int cnt=0;
for(int i=1;i<n;i++)
{
if(a[i]-a[i-1]>x)//要添加路标
{
int t=(a[i]-a[i-1]);//计算要添加几个
cnt+=t/x;
if( (t%x) ==0) cnt--;//整除的话,减去多加的1次
}
}
if(cnt<=m) return true;
else return false;
}
int main(void)
{
cin>>sum>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
int l=0,r=sum+1;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
P1182 数列分段 Section II 【有意思】
https://www.luogu.com.cn/problem/P1182
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int a[N],n,m;
int l,r;
bool check(int x)
{
int cnt=0;
int k=0;//记录分的段数
for(int i=0;i<n;i++)
{
if(cnt+a[i]>x)
{
cnt=0;
k++;
}
cnt+=a[i];
}
if(k<m) return true;
else return false;
}
int main(void)
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i],r+=a[i],l=max(l,a[i]);
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
P3743 kotori的设备 【有意思 / long double / check有意思】
https://www.luogu.com.cn/problem/P3743
注意用 long double 不然有一个点过不去
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
struct node
{
int a,b;//a是花费,b是当前电
}Node[N];
int n,p;
bool check(long double x)
{
double sum=p*x;//总的充电量
for(int i=0;i<n;i++)
{
double temp=Node[i].a*x;
if(temp>Node[i].b) sum=sum+Node[i].b-temp;//要充电
}
if(sum>0) return true;//冲的电够用
else return false;//冲的不够用
}
int main(void)
{
cin>>n>>p;
long long int sum=0;
for(int i=0;i<n;i++) cin>>Node[i].a>>Node[i].b,sum+=Node[i].a;
if(sum<=p)//消耗量小于等于冲的量,可以无限工作
{
cout<<-1<<endl;
return 0;
}
long double l=0,r=1e10;
while(r-l>1e-9)
{
long double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%llf\n",l);
return 0;
}