目录
一、二分
1.对不同板子的理解
【听说二分很容易写歪,细节要注意,接下来的几种写法,建议是选中一个自己熟悉的就一直只写这个,免得写混,不过还是要好好理解】
要避免“死循环”的出现,就是要让每一次的执行都有所改变,而不是判断之后相当于什么也没做,具体到代码就是 l 和 r 要移动,那为什么会死循环呢?因为我们的 mid = ( l + r )/2是“整除”啊,是“向下取整”,当 r==l+1 时,mid 会等于其中较小的那一个,也就是 l ,这时候如果右边的代码也写成 mid = ( l + r )/2 ,那么会在 l = mid 这里进入死循环(把 l 赋值为 l ,你干了个啥.)【可以拿数列 3 3 3 做下模拟】关于最终答案:找等号嘛,最后一次执行的mid就是x的下标啦,然后左边的就是 r ,右边的就是 l.
再来看一类写法:直接舍弃 mid ,不考虑那么多,每次 l = mid + 1 , r = mid - 1 , 那么区间就一定是在缩小的:
注意啦!这里while的条件里要等号
那,我们最终答案写什么,看等号,对于上图左边的代码,if ( a[mid]>= x) r = mid - 1 ;最终 mid 是答案 所以 我们输出的就是 r + 1, 或者可以看下面那一句,a[mid] < x 时, l = mid +1 ,这里最后一次的mid 小于 x ,那就是比x小1,所以答案也是 l , 右边就是 l - 1 ,或者 r.
还有就是,(l+r)有一种更安全的写法来防止溢出的:l + (r-l)/2 ;
lower_bound是找大于等于x的第一个数的位置,upper_bound可用来找大于x的第一个数,记得减去数组名(因为返回的是迭代器(类似指针),减去数组名(首地址)就得到下标了)。。
【上面 就算排序下标从1开始,后面也不要多减一...因为你减的是地址得到一个你现在的下标,本来就是从多一的开始了,自然没必要减啊。。】
例题一:
A-[USACO 2009 Dec S]Music Notes_
题意:牛依次敲(奏)n个音符,给出每个音符分别要(连续)弹奏多长时间,Q次询问,每次询问给个T,问T~T+1时间牛牛们要敲哪个音。
思路:一个前缀和,然后标准二分思路,都不用自己写了,用现成函数就好。
AC代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int M=5e4+6;
int n,q,a[M];
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q;
int i,x,t;
for(i=1;i<=n;i++){
cin>>x;
a[i]=a[i-1]+x;
}
while(q--){
cin>>t;
cout<<upper_bound(a+1,a+1+n,t)-a<<'\n';
}
return 0;
}
超级经典二分题!!!Drying !
Drying - POJ 3104 - Virtual Judge (vjudge.net)
题意:有 n 件衣服,每件衣服含有一定水分,有自然晾干和拿吹风机吹干两种方式,晾干是每分钟减少一单元水,吹风机是每分钟使其减少 k 份水(这一分钟只能吹一件,不能换另一件衣服吹,衣服水分变为0后不会有变化)问最少多少分钟能把衣服全部弄干。
思路:这题满足单调性,即可二分结果,假设x时间为答案,那么比答案大的(判断的是"mid"时间),计算出的要花的时间就会比你预想的时间mid 少(即有时间剩余,所以我们这个答案大了,就该缩小,区间右边界左移,反之亦然)
虽然说这里的k是包含了自然风干的水分的(即这段时间干的就是k不是k+1)但千万注意这里除的是 (k-1),www为什么呢,咱就是说凡事推推公式 好处多多 一目了然,不要想当然。->设总时间为x , t 为吹风机时间,kt + (x-t) >=a[i] , kt-t >= a[i]-x , (k-1)t >=a[i]
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M=1e5+6;
ll n, k, a[M], sum;
bool check(int x){
int i=upper_bound(a+1,a+1+n,x)-a;//找到第一个需要使用吹风机的位置
sum=0;
for(;i<=n;i++)sum+=(a[i]-x+k-2)/(k-1);//加个k-2是为了向上取整,也可以用ceil函数
return sum<=x?1:0;//计算的时间比设定的小,就返回真
}
int main(){
scanf("%lld",&n);//POJ卡cin....
for(int i=1;i<=n;i++)scanf("%lld",a+i);
sort(a+1,a+1+n);
scanf("%lld",&k);
if(k==1){printf("%lld",a[n]);return 0;}
int l=1, r=1e9;
while(l<r){//二分板子之一,不熟悉就看看上文吧
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
printf("%d",r);
return 0;
}