目录
【深基13.例1】查找
代码1:手打二分(要记住二分模板)
#include<iostream>
using namespace std;
const int N=1e6+10;
int s[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)cin>>s[i];
while(m--)
{
int q;
cin>>q;
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(s[mid]>=q)r=mid;
else l=mid+1;
}
if(s[l]==q)cout<<l+1<<' ';
else cout<<"-1"<<' ';
}
return 0;
}
代码2:STL(lower_bound)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
while(m--)
{
int x;
cin>>x;
int b=lower_bound(a,a+n,x)-a; //返回不小于目标值的第一个元素的下标
if(a[b]==x)cout<<b+1<<" ";
else cout<<"-1"<<" ";
}
return 0;
}
眼红的Medusa
注意是按在科技创新奖获奖名单中的先后次序输出
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
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(b,b+m);
for(int i=0;i<n;i++)
{
int x=lower_bound(b,b+m,a[i])-b;
if(b[x]==a[i]) cout<<a[i]<<' ';
}
return 0;
}
A-B 数对
如果这个数组是有序的,那么对于每一个A的值,在它的后方就只有一个数值B满足A,B的差值为C。这时我们只需要使用两个函数求出数组中对于每个A,A+C的这两个位置,它们的差即为数组中数值为A+C的元素个数。将这个数加到res中。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int s[N];
int main()
{
int n,c;
cin>>n>>c;
for(int i=0;i<n;i++)cin>>s[i];
sort(s,s+n);
long long res=0;
for(int i=0;i<n;i++)res+=upper_bound(s,s+n,s[i]+c)-lower_bound(s,s+n,s[i]+c);//题目要求:(不同位置的数字一样的数对算不同的数对)。
cout<<res;
return 0;
}
银行贷款
关键是要知道怎么求利息,对于这道题了类型可以参看一下数的三次方根
代码:
#include<bits/stdc++.h>
using namespace std;
double n,m,q; //注意数据类型!!!!,pow(double,double)
bool check(double x){
return (pow(1.0/(1.0+x),q)>=1-n/m*x);
}
int main()
{
cin>>n>>m>>q;
double l=0,r=10;
while(r-l>1e-8)
{
double mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
double res=l*100;
printf("%.1f",res);
// cout<<fixed<<setprecision(1)<<l*100; 输出一位小数
return 0;
}
烦恼的高考志愿
二分:对于每一个成绩,二分找出第一个大于等于该成绩的数,相差最小的出现在左边的数小于该成绩,右边的数大于该成绩。
代码1:手打二分
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n);
for(int i=0;i<m;i++)cin>>b[i];
int ans=0;
for(int i=0;i<m;i++)
{
int l=0,r=n;
while(l<r)
{
int mid=l+r>>1;
if(a[mid]<=b[i])l=mid+1;
else r=mid;
}
if(a[0]>=b[i])ans+=a[0]-b[i]; //有可能找不到返回最左边的值,所以要判断一下
else ans+=min(abs(a[l]-b[i]),abs(a[l-1]-b[i]));
}
cout<<ans;
return 0;
}
代码2:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n);
int ans=0;
for(int i=0;i<m;i++)
{
int x;
cin>>x;
int s=lower_bound(a,a+n,x)-a;
if(a[0]>=x)ans+=a[0]-x;
else ans+=min(abs(a[s]-x),abs(a[s-1]-x));
}
cout<<ans;
return 0;
}
Cellular Network
对于每一个城市,覆盖它的必然是x坐标与它最相近的两座塔之一,那么我们就可以二分来用城市找塔,在相邻两塔与它的距离中取min,再取所有城市的max就是答案了
代码:
#include<bits/stdc++.h>
using namespace std;
int a[100005], b[100005];
int n, m;
int main() {
int ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> a[i];
for (int i = 1; i <= m; i++)cin >> b[i];
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + m);
a[n + 1] = b[m + 1] = 1e9,a[0] = b[0] = -1e9; //设个边界,防止溢出。
for (int i = 1; i <= n; i++)
{
int x;
x = lower_bound(b + 1, b + 1 + m, a[i]) - b;
ans = max(ans,min(b[x] - a[i],a[i] - b[x - 1]));
}
cout << ans;
return 0;
}
max()赋给int变量
Eating Queries
根据题意,可以优先吃最大的糖果,将数组从大到小进行排列,求数组的前缀和,则只需要找到第一个 ≥x即可。(下标即代表最少吃糖的个数)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],s[N];
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
while(q--)
{
int x;
cin>>x;
if(s[n]<x)cout<<"-1"<<endl;
else cout<<lower_bound(s+1,s+n+1,x)-s<<endl;
}
}
return 0;
}
[COCI 2011/2012 #5] EKO / 砍树
二分答案问题:提高伐木机的高度,显然地,得到的木头会减少,同样地,放低得到的木头会增多,而正因为答案有单调性,所以我们可以使用二分。
选定一个答案mid,对数组进行遍历,将高出答案的部分进行累加,累加的和与木材总长度比较进行区间更新。
代码:(学到一个求数组最大元素的小技巧)
#include<bits/stdc++.h>
typedef long long LL;
LL n,m,x,l,r,mid,s; //注意需要用long long
const int N=1e6+10;
int a[N];
using namespace std;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
x= *max_element(a,a+n); //取最大值定义为右边界
l=0,r=x;
while(l<r)
{
mid=l+r>>1;
s=0;
for(int i=0;i<n;i++)
if(a[i]>mid)s+=a[i]-mid; //高出的部分进行累加
if(s<m)r=mid; //高出总木材的长度时
else l=mid+1;
}
cout<<l-1;
return 0;
}
木材加工
对切割的长度进行二分,得到的切割数量与输入的需要得到的小段的数量比较进行区间更新
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
LL a[N];
LL n,k;
bool check(LL x)
{
LL num=0;
for(int i=0;i<n;i++)
{
num+=a[i]/x; //c++自带整除,也可以用floor
}
return num>=k;
}
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)cin>>a[i];
LL l=0,r=*max_element(a,a+n);
while(l<r)
{
LL mid=l+r+1>>1;
if(check(mid)) l=mid; //切得多或刚好等于,则说明还有可能长度更长
else r=mid-1; //切得少,说明长度太大了
}
cout<<l;
return 0;
}
切绳子
和上面木材加工类似
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
double a[N];
double n,k,l,r;
bool check(double x)
{
double num=0;
for(int i=0;i<n;i++)
{
num+=floor(a[i]/x); //c++自带整除,也可以用floor
}
return num>=k;
}
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)cin>>a[i],r+=a[i];
for(int i=0;i<100;i++) //注意不是用while(r-l>1e-8)否则只得93,因为精度不够,需要多循环几次
{
double mid=(l+r)/2;
if(check(mid)) l=mid; //切得多或刚好等于,则说明还有可能长度更长
else r=mid; //切得少,说明长度太大了
}
double s=int(l*100); //保留两位小数
//double a=s/100.0;
printf("%.2f",s/100.0);
return 0;
}
[NOIP2015 提高组] 跳石头
二分答案:最小距离的最大值——最小距离一定出现在相邻石头之间。从起点出发,先选定一段距离mid,若前面的石头B与你所站着的石头A的距离小于mid(因为mid是最小的,如果有比mid小的就搬掉使其变大),就把B搬掉,记录一下;如果不,就把B留下,再跳到石头B上。照这个步骤多次循环后,如果搬掉和m比较,如果多了,就把距离mid定小点;如果少了,就把mid定大点。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL L,n,m,l,r,mid;
const int N=50010;
int a[N];
int main()
{
cin>>L>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
a[n+1]=L; //注意是n+1
l=0,r=L;
int ans=0;
while(l<r)
{
mid=l+r+1>>1;
int now=0,k=0; //now代表跳石头的人当前在什么位置
for(int i=1;i<=n+1;i++) //注意n+1
{
if(a[i]-a[now]<mid) k++; //判定成功,把这块石头拿走,继续考虑下一块石头
else now=i; //判定失败,这块石头不用拿走,我们就跳过去,再考虑下一块
}
if(k<=m)l=mid,ans=mid; //记录答案(更新中)
else r=mid-1;
}
cout<<ans;
return 0;
}
[USACO06DEC]River Hopscotch S
同跳石头
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL L,n,m,l,r,mid;
const int N=50010;
int a[N];
int main()
{
cin>>L>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
a[n+1]=L; //注意是n+1
l=0,r=L;
int ans=0;
while(l<r)
{
mid=l+r+1>>1;
int now=0,k=0;
for(int i=1;i<=n+1;i++) //注意n+1
{
if(a[i]-a[now]<mid) k++;
else now=i;
}
if(k<=m)l=mid,ans=mid;
else r=mid-1;
}
cout<<ans;
return 0;
}
进击的奶牛
同跳石头
代码:
#include<bits/stdc++.h>
using namespace std;
int n,c,l,r,mid;
const int N=1e5+10;
int a[N];
int main()
{
cin>>n>>c;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
l=0,r=a[n]-a[1];
while(l<r)
{
mid=l+r>>1;
int now=1,k=1;
for(int i=1;i<=n;i++)
{
if(a[i]-a[now]>=mid) k++,now=i; //如果两个隔间的距离大于等于mid时,就在i上一头牛,然后当前的位子改为i
}
if(k<c) r=mid;
else l=mid+1;
}
cout<<l-1;
return 0;
}
数列分段 Section II
二分答案:对于二分的值mid,我们从数列a从前往后扫,如果区间总和t大于了mid,我们不加而是t重新赋值并且隔板num++,最后只需判断num是否不小于m就行了
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int n,m,l,r,mid;
bool check(int x)
{
int t=0,num=1;
for(int i=1;i<=n;i++)
{
if(t+a[i]<=x) t+=a[i];
else t=a[i],num++;
}
return num<=m;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i],l=max(a[i],l),r+=a[i];
while(l<r)
{
mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
[TJOI2007]路标设置
同数列分段
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int n,k,L;
bool check(int x)
{
int num=0;
for(int i=1;i<n;i++)
{
if(a[i]-a[i-1]>=x)
{
num+=(a[i]-a[i-1])/x; //如果原有路标的距离大于mid,计算需要增加的路标的个数
if((a[i]-a[i-1])%x==0)num--; //如果原有路标的距离等于mid,则不需要增加
}
}
return num<=k; //增加的路标大于最多可增设的路标数量,说明距离大,需要变小
}
int main()
{
cin>>L>>n>>k;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n); //注意排序
int l=0,r=L+1;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
奶牛晒衣服
对弄干衣服的时间进行二分,mid作为自然干的时间,每一次二分都对应一个实际的烘干时间num,将mid和num比较进行区间更换
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5*1e5+10;
int s[N];
int n,a,b;
bool check(int x)
{
int m=0,num=0;
for(int i=0;i<n;i++)
{
int k=0;
m=s[i]-a*x; //不进烘干机 也能减轻a*mid的湿度
if(m<=0) continue; //已经干了
else
{
k=m/b; //没干k为烘干时间
if(m%b!=0)k++; //小数时
num+=k; //num为需要的总的烘干时间
}
}
return num<=x; //mid大于实际烘干时间,mid取大了
}
int main()
{
cin>>n>>a>>b;
for(int i=0;i<n;i++) cin>>s[i];
int l=0,r=N;
while(l<r)
{
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
kotori的设备
同奶牛晒衣服
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1*1e5+10;
double a[N],b[N];
int n;
double q,s;
bool check(double x)
{
double m=0,num=0;
for(int i=0;i<n;i++)
{
m=a[i]*x;
if(m<=b[i]) continue; //若设备已有的能量大于使用时间需要的能量,忽略该设备
else
{
num+=(m-b[i]); //否则用充电器充电,使设备已有的能量等于使用时间需要的能量,并记录需要的能量。
}
}
return num<=q*x; //最后比较需要的能量总和和充电器最多提供的能量。
}
int main()
{
cin>>n>>q;
for(int i=0;i<n;i++) cin>>a[i]>>b[i],s+=a[i];
if(s<=q)
{
cout<<"-1"; //若所有设备的消耗能量速度总和还是小于充电器的充电速度,输出-1。
return 0; //必须return 0
}
double l=0,r=1e10;
while(r-l>1e-6) //注意不能是1e-8
{
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
cout<<l;
return 0;
}
卡牌
分两种情况讨论:
1.空白牌足够多,n种牌中原有牌的数量加上能手写牌的数量的和(c[i]=a[i]+b[i])最小值(c[i])是在不全手写的套牌数的最小值,总的空白牌减去用过的除以n,是额外加的套牌数
2.空白牌不足够多或最多手写的张数足够多,二分套牌数,每次都计算一下需要的空白牌,最后判断空白牌是否大于等于0,即够用
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=2*1e5+10;
LL n,m;
LL a[N],b[N],c[N];
bool check(int x)
{
LL k=m;
for(int i=0;i<n;i++)
{
if(x<a[i])continue;
else if(x-a[i]<=b[i])
k-=(x-a[i]);
else if(x-a[i]>b[i])return false;
}
//cout<<"k="<<k<<endl;
return k>0;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)cin>>b[i],c[i]=b[i];
LL l=*min_element(a,a+n),r=*max_element(a,a+n)+m/n;
LL s=*max_element(a,a+n),y=m;
for(int i=0;i<n;i++)
{
if(a[i]<s)
{
if(s-a[i]<=c[i])
{
y-=(s-a[i]);
c[i]-=(s-a[i]);
}
}
if(y<0||c[i]<0)break;
}
int t=*min_element(c,c+n);
if(y>=0&&t>=0)
{
s+=y/n;
cout<<s;
return 0;
}
while(l<r)
{
LL mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l;
return 0;
}
放书
二分选择的书的数量mid,每次二分都计算一下当前情况下空位能够放下的书的数量num,在与选择的书的数量比较,进行区间更新
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL a,b,n,m,h;
bool check(LL x)
{
LL num=(n+m-x)/b*(h-b)+(n/b)*(b-a); //空位能够放下的书的数
return num>=x;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>a>>b>>n>>m>>h;
LL l=0,r=m-1;
while(l<r)
{
LL mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<n+m-l<<endl;
}
return 0;
}
Chat Ban
将小A发送的信息条数分为三种情况:
-
小A能够发送的信息条数 <=n 条
-
小A能够发送的信息条数 <=n条且<=2n-1 条
-
小A能够成功发送整个符号三角形
依据上面的分类得出:
-
对于第一种情况,直接查找小A能够发送的信息条数 x ,此时 x 满足 f(n-1)<x<= f(n)。
-
对于第二种情况,反面考虑,查找管理员在他还差 y 条信息没发送时将他禁言,此时 yy满足 f(n-1)-1<=y<f(n)-1。
-
对于第三种情况,直接输出2n−1 即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL k,x;
int main()
{
LL t;
cin>>t;
while(t--)
{
cin>>k>>x; //k表示发消息的个数,x表示最多发送的符号
if(((1+k)*k/2+(1+k-1)*(k-1)/2)<x)cout<<2*k-1<<endl; //消息全部发出的符号总数小于x时
else
{
LL l=0,r=2*k; //二分
while(l<r)
{
LL mid=l+r>>1;
LL s=0,ss=0; //s表示能发出mid个消息的总符号,ss表示能发出mid-1个消息的总符号,ss<x<=s才符合条件
if(mid<=k)s=((1+mid)*mid)/2,ss=(mid*(mid-1))/2;
else s=((1+k)*k)/2+(mid-k)*(k-1+k-(mid-k))/2,ss=((1+k)*k)/2+(mid-k-1)*(k-1+k-(mid-1-k))/2;
if(s>=x) r=mid; //mid个消息的总符号大于x时
else if(ss<x)l=mid+1; //mid-1个消息的总符号小于x时
else //ss<x<=s才符合条件
{
l=mid;
break;
}
}
cout<<l<<endl;
}
}
return 0;
}
Binary String
思路参看:(282条消息) CF1680 C. Binary String(贪心)_樱落二瓣七里香的博客-优快云博客
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
string str;
int s[N];
int main()
{
int T;
cin>>T;
while(T -- )
{
cin>>str;
int n = str.size(), cnt = count(str.begin(), str.end(), '1'); //cnt为字符串中1的个数S1
str = " "+str;
for(int i = 1; i<=n; i++)
s[i] = s[i-1] + (str[i] == '0'); //s[]为前n个子串中0的个数
int res = cnt;
for(int i = cnt; i<=n; i++)
{
res = min(res, s[i] - s[i-cnt]); //i和i-cnt为长度是cnt的子串,求该子串中0的个数的最小值
}
cout<<res<<endl;
}
return 0;
}