一、第一题
代码:
#include<iostream>
#define N 100000
using namespace std;
int main()
{int n, q, i, l, r,mid;long long m, a[N];cin >> n;for (i = 0; i < n; i++)cin>>a[i];cin >> q;for (i = 0; i < q; i++){cin >> m;l = 0, r = n - 1;while (l < r){mid = (l + r) / 2;if (a[mid] >= m)r = mid;else l = mid + 1;}if (a[l] != m)cout << "No\n";elsecout << "Yes\n";}return 0;
}
思路:若mid位置的数大于等m,范围向左缩小,反之向右缩小,通过二分查找实现在有序数组中寻找m的功能
二、第二题
代码:
#include<iostream>
#include<algorithm>
#define N 200000
using namespace std;
int main()
{int n, i, l, r,mid,ans;long long c, a[N],count=0;cin >> n>>c;for (i = 0; i < n; i++)cin>>a[i];sort(a, a + n);for (i = 0; i < n; i++){l = i + 1, r = n - 1;while (l < r){mid = (l + r) / 2;if (a[mid] - a[i] >= c) r = mid;else l = mid + 1;}if (a[l] - a[i] == c) ans = l;else continue;l = ans, r = n - 1;while (l < r){mid = (l + r + 1) / 2;if (a[mid] <= a[ans]) l = mid;else r = mid - 1;}count += l - ans + 1;}cout << count;return 0;
}
思路:先通过sort函数将数组升序排列,之后遍历每一个元素,分别找每个元素接下来是否有符合距离为c的数,若有则继续二分查找该数最后出现的位置,最后位置与初始位置的差值即为个数对
三、第三题
代码:
#include<iostream>
#define N 100000
using namespace std;
int h[N], w[N],k,n;
int check(int mid)
{int i,length,width,count=0;for (i = 0; i < n; i++){length = h[i] / mid; width = w[i] / mid;count += length * width;}if (count >= k) return 1;else return 0;
}
int main()
{int i, l, r,mid;cin >> n>>k;for (i = 0; i < n; i++)cin >> h[i]>>w[i];l = 1, r = 100000;while (l < r){mid = (l + r + 1) / 2;if (check(mid)) l = mid;else r = mid - 1;}cout << l;return 0;
}
思路:巧克力边长范围为一到十万,通过二分查找找到符合题意的最大值,如果以该边长进行切割,所有巧克力能切出的个数总和大于等于人数k,该边长就符合题意
四、第四题
代码:
#include<iostream>
#define N 200000
using namespace std;
int a[N], b[N],n;
long long m;
int check(int mid)
{long long count=0;for (int i = 0; i < n; i++)if(a[i]<mid) {count+=mid-a[i];if(mid-a[i]>b[i])return 0;}if (count <=m) return 1;else return 0;
}
int main()
{int i, l, r,mid,min=200001,minn;cin >> n>>m;for (i = 0; i < n; i++){cin >> a[i];if(a[i]<min) min=a[i],minn=i;}for (i = 0; i < n; i++)cin >> b[i];l = min, r = min+b[minn];while (l < r){mid = (l + r + 1) / 2;if (check(mid)) l = mid;else r = mid - 1;}cout << l;return 0;
}
思路:先找到数组中个数最少的牌,那么能凑出的牌数范围就是个数最少的牌的数量到个数最少的牌数加上可用牌数之和,在这个范围里二分查找mid答案,若每种牌要达到mid个数需要加进去的牌的总数之和没有超过拥有的牌,该mid就符合题意,同时要注意每种牌到达mid数时所加牌的数量是否在自己可加牌数范围内
五、第五题
代码:
#include<iostream>
using namespace std;
int a[500],m,k;
int check(int mid)
{int sum=0,count=1;for(int i=0;i<m;i++){if(sum+a[i]<=mid) sum+=a[i];else{sum=a[i];count++;}}if(count<=k) return 1;else return 0;
}
int main()
{int i, l, r,mid,max=0,sum=0;cin >> m>>k;for (i = 0; i < m; i++){cin >> a[i];if(a[i]>max) max=a[i];sum+=a[i]; }l = max, r = sum;while (l < r){mid = (l + r) / 2;if (check(mid)) r = mid;else l = mid + 1;}int first[k],second[k],total=0,j=k-1,f=m;for(i=m-1;i>=0;i--){if(total+a[i]<=l) total+=a[i];else{second[j]=f;total=a[i];first[j--]=i+2;f=i+1;}}second[j]=f,first[j]=i+2;for(i=0;i<k;i++)cout<<first[i]<<' '<<second[i]<<endl;return 0;
}
思路:书本数组中,所花时间的范围是最多页的书到所有书页数的总和,在这个范围内二分查找mid,若将数组最大程度分为连续总和不超过mid的几段,若段的个数小于等于k个人,证明mid符合题意,然后建立first和second数组来存储每个人需要抄写的书的范围,通过倒序遍历数组,将数组尽可能的分为连续总和不超过所求l的k段,倒序记录每段的开头结尾存入first和second数组,倒序是为了满足多解情况下让前面的人尽可能少写,最后正序输出first和second数组
六、第六题
代码:
#include<iostream>
#define N 100000
using namespace std;
int h[N],n;long long x;
int check(int mid)
{long long sum=0,i;for(i=0;i<mid;i++)sum+=h[i];if(sum<x) return 0;for(i=mid;i<n-1;i++){sum+=h[i];sum-=h[i-mid];if(sum<x) return 0;}return 1;
}
int main()
{int i, l, r,mid;cin >> n>>x;x*=2;for (i = 0; i < n-1; i++)cin >> h[i];l = 1, r = n;while (l < r){mid = (l + r) / 2;if (check(mid)) r = mid;else l = mid + 1;}cout<<l;return 0;
}
思路:因为往返本质上是一样的,因此可以看成青蛙要能走这段路走2x次,不用考虑方向,青蛙的跳跃能力范围在1到河宽n之间,通过二分查找找到最小的mid答案,因为青蛙能跳跃的点在一到跳跃能力y之间,即一到y之间的石头h的总和要大于等于2x,青蛙才能顺利完成2x次跳跃,即每1到y范围内的石头h总和都要大于等于2x,若mid符合这一点就符合题意;check函数的思路是先算出最开始的mid个数内的总和,然后开始从mid往下遍历每一个数,每次对sum进行加减更新以保证sum一直是mid个数的元素之和,每一次都对sum进行判断是否大于等于2x
学习总结:二分法的学习过程中,首先要牢记二分模板,灵活运用到不同题型中,在解题过程中应先判断需要二分的对象,接着判断check函数的判定条件;在青蛙过河题中,我明白了应对题目所给的区间有高敏感,进行大胆猜测