P2249 查找
在数组中查找点,可以直接使用模板一,也可以使用模板二
#include<iostream> using namespace std; const int N = 1e6 + 10; int a[N]; int n, m; int find(int k) { int l = 0, r = n - 1; int mid; while(l < r) { mid = l + r >> 1; if(a[mid] >= k) { //查找大于等于k的第一个元素,其实就是k本身 r = mid; } else { l = mid + 1; } } if(a[r] == k) { //如果最终的结果查找正确,返回r+1,因为数组下标是从零开始的,而题目要求从1编号 return r + 1; } else { return -1; } } int main(){ cin >> n >> m; for(int i = 0; i < n; ++i) { cin >> a[i]; } while(m --) { int k; cin >> k; cout << find(k) << " "; } return 0; }
P1102 A-B 数对
\(A-B = C\)不好求,我们可以转化成\(A=B+C\),《挑战程序设计竞赛》上讲过这个黑科技。
所以我们可以使用一个循环枚举\(B\),如果数组有序,那么在\(B\)的后方会存在一个数满足\(A-B=C\),所以我们只需要找出从当前位置到\(n\)中,小于等于\(B+C\)和小于\(B+C\)的位置,然后再将其相减就得到了数对的个数,每次循环我们都需要将这个差值累计起来,最终这个累计值就是答案了。
#include<iostream> #include<algorithm> using namespace std; const int N = 2e5 + 10; int a[N]; long long ans = 0; int find_left(int l, int r, int k) { while(l < r) { int mid = l + r + 1 >> 1; if(a[mid] < k) { //寻找小于k的最后一个元素 l = mid; } else { r = mid - 1; } } return l; } int find_right(int l, int r, int k) { while(l < r) { int mid = l + r + 1 >> 1; if(a[mid] <= k) { //寻找小于等于k的最后一个元素 l = mid; } else { r = mid - 1; } } return l; } int main(){ int n, c; cin >> n >> c; for(int i = 1; i <= n; ++i) { cin >> a[i]; } sort(a + 1, a + 1 + n); //必须要先排序 for(int i = 1; i <= n; ++i) { int l = find_left(i, n , a[i] + c); //从i到n查找a[i] + c int r = find_right(i, n, a[i] + c); cout << l << " " << r << endl; ans += r - l; //如果不存在的话,r - l就是零 } cout << ans; return 0; }
P1873 砍树
题目中有一句话“如果再升高1米,则他将得不到M米木材”,其实这个地方就暗示我们答案是满足最大化最小(小于M的最大值)这一条性质,那么就直接二分答案,二分边界l=0, r=max(树的高度),每次枚举一个\(H\),看是否满足题意:
#include<iostream> using namespace std; const int N = 1000005; long long a[N]; long long m, n; long long check(int H) { long long sum = 0; for(int i = 0; i < n; ++i) { if(a[i] > H) //如果可以砍 sum += a[i] - H; //将获得的木材累计起来 } return sum; } int main(){ cin >> n >> m; int _max = -1; for(long long i = 0; i < n; ++i) { cin >> a[i]; if(a[i] > _max) _max = a[i]; } long long l = 0, r = _max; while(l < r) { long long mid = l + r >> 1; if(check(mid) < m) { //如果木材少了 r = mid; //减少H,增加木材 } else { l = mid + 1; } } cout << r - 1 << endl; return 0; }
P1024 一元三次方程求解
其实博主最早接触的二分应该就是这个例子,貌似是高一数学里面的内容,大家应该也都学过,但是后面用的很少(基本不用)。
因为根与根之间的绝对值大于等于1,所以说我们可以每次二分一个区间,这个区间的端点就是两个相邻的整数,而答案就是:要么在区间端点,这个我们可以提前特判一下,要么在区间内部,此时\(f(l)\times(r) < 0\)
#include<iostream> #include<vector> #include<algorithm> #include<cstdio> using namespace std; double a, b, c, d; double f(double x) { return a * x * x * x + b * x * x + c * x + d; } vector<double> ans; int main(){ cin >> a >> b >> c >> d; for(int i = -100; i <= 100; ++i) { double l = i, r = i + 1; //每次枚举一个区间 if(f(l) == 0) { ans.push_back(l); } if(f(l) * f(r) < 0) { //如果发现两根之积为负数,则说明这个区间有根 while(r - l >= 0.000001) { //实数二分随便写 double mid = (l + r) / 2; if(f(l) * f(mid) <= 0) { r = mid; } else { l = mid; } } ans.push_back(l); } if(ans.size() == 3) break; } sort(ans.begin(), ans.end()); printf("%.2lf %.2lf %.2lf", ans[0], ans[1], ans[2]); return 0; }
P1678 烦恼的高考志愿
首先对每所大学的分数线排序,然后对于每个学生,查找刚好比他分数线高一点的大学,然后在前一个大学和这个分数线高一点的大学中选择最优的,思路非常简单,直接看代码就能看懂了。
#include<iostream> #include<cmath> #include<algorithm> #include<vector> using namespace std; const int N = 1e6 + 10; vector<int> a; int b[N]; int n, m; int find(int k) { int l = 0, r = m - 1; while(l < r) { int mid = l + r >> 1; if(a[mid] >= k) { //寻找大于等于k的第一个元素 r = mid; } else { l = mid + 1; } } return r; } int main(){ cin >> m >> n; for(int i = 0; i < m; ++i) { int k; cin >> k; a.push_back(k); } for(int i = 0; i < n; ++i) { cin >> b[i]; } int ans = 0; sort(a.begin(), a.end()); for(int i = 0; i < n; ++i) { int k = find(b[i]); if(b[i] <= a[0]) { //如果是在最左边 ans += a[0] - b[i]; //直接是第一个 } else { ans += min(abs(a[k - 1] - b[i]), abs(a[k] - b[i])); //在前一个和后一个中选择最优的 } } cout << ans << endl; return 0; }
P2440 木材加工
我们直接二分答案,答案的最小值当然就是零,答案的最大值也就是题目告诉我们的“每行有一个1到100000000”之间的正整数,那我们就直接设置\(r=max+1\)就好了,然后我们搜索每个长度,当然是二分搜索了,然后看能不能符合题目中要获得的小段数目就好了。注意这里要求的是满足答案的最大值,所以我们用第三套模板。
#include<iostream> using namespace std; const int N = 100005; long long a[N]; int n; long long k; int check(int len) { long long cnt = 0; for(int i = 0; i < n; ++i) { cnt += a[i] / len; //看能切多少段 } return cnt; } int main(){ cin >> n >> k; long long sum = 0; for(int i = 0; i < n; ++i) { cin >> a[i]; } long long l = 0, r = 100000001; while(l < r) { int mid = l + r + 1 >> 1; if(check(mid) >= k) { //如果段数多了 l = mid; //增大长度,让段数变少 } else { r = mid - 1; //否则减小长度,让段数变多 } } cout << l << endl; //到最后肯定是l==r退出循环,所以也可以输出r return 0; }
P2678 跳石头
这题当然也是直接二分答案了,最少是零,最大是L,然后我们枚举答案,看在当前答案下,最多可以移走多少块石头,然后和题目中给定的移走石头数目比较。这里也是求最小的最大,所以用第三套模板
#include<iostream> using namespace std; long long L, N, M; const int n = 50005; long long a[n]; long long check(long long k) { //跳跃距离为k时,要移走多少块石头 long long now = 0, ans = 0; for(int i = 0; i < N; ++i) { if(a[i] - now < k) { //如果能移,就移走 ans++; } else { now = a[i]; //否则我们就跳过去,更新now就好了 } } return ans; } int main(){ cin >> L >> N >> M; for(int i = 0; i < N; ++i) { cin >> a[i]; } long long int l = 0, r = L; while(l < r) { long long mid = l + r + 1 >> 1; if(check(mid) <= M) { //如果当前跳跃距离下移走的石头小于组委会最多可以移走的石头 l = mid; //那就说明可以跳更远一点,这样的话可以移走更多石头 } else { r = mid - 1; //这个解行不通,得少跳一点 } } cout << l << endl; return 0; }
P3853 [TJOI2007]路标设置
我们二分搜索答案,假设空旷指数最少是零,最多是L,在当前的空旷指数下,看我们还需要增设多少个路标,如果增设的路标小于“最多可增设的路标数量”,则说明我们需要减少空旷指数,这样才能不浪费路标嘛。具体的细节看代码:
#include<iostream> using namespace std; const int n = 100005; int a[n]; int check(long long K) { //空旷指数为k时,要增加路标的个数 int ans = 0; for(int i = 0; i <= n; ++i) { if(a[i + 1] - a[i] > K) { //如果两个路标之间的空旷指数大于了K,那就不行了啊,得插路标了 ans += (a[i + 1] - a[i]) / K; //路标的个数就是后面的距离减去前面的距离然后除以K if((a[i + 1] - a[i]) % K == 0) //如果刚好是倍数,那么会和一个端点重合,要减去1 ans--; } } return ans; } int main() { long long L, N ,K; cin >> L >> N >> K; for(int i = 1; i <= N; ++i) { cin >> a[i]; } a[0] = 0, a[N + 1] = L; //两端默认的路标可别忘了 long long l = 0, r = L; //增设的路标越多,空旷指数越小 while(l < r) { long long mid = l + r >> 1; if(check(mid) <= K) { //当空旷指数为mid时,需要增设的路标个数 r = mid; } else { l = mid + 1; } } cout << l << endl; return 0; }
P1182 数列分段 Section II
搜索答案,对于每个答案,看看能不能分出M段,如果少于M段,那就需要我们将答案调大,减小段和值,增加段数;如果段数多了,那我们就需要将答案增大,增大段和值,减少段数。具体的细节看代码
#include<iostream> using namespace std; const int N = 1e5 + 5; long long a[N]; int n, m; int check(int k) { int sum = 0, cnt = 0; for(int i = 0; i < n; ++i) { if(sum + a[i] <= k) //如果这个加上这个数,段的和的值还小于k sum += a[i]; //那就把他加上 else { sum = a[i]; //否则用这个数重新将sum更新,然后重新分段 cnt++; } } return cnt; } int main(){ cin >> n >> m; long long sum = 0; long long _max = 0; for(int i = 0; i < n; ++i) { cin >> a[i]; if(a[i] > _max) _max = a[i]; //找最大值作为区间的左端点,因为无论如何l也不可能小于一个数的最大值 sum += a[i]; //找最大值 } long long l = _max, r = sum; while(l < r) { int mid = l + r >> 1; if(check(mid) < m) { //如果段数小了,那么减小段和值,增加段数 r = mid; } else { l = mid + 1; } } cout << l << endl; return 0; }
P1163 银行贷款
其实这个题是有公式的,如果知道了公式,那就简单多了,由于我们这里的重点在于写二分,而不是去研究什么利率之类的东西,我们直接根据公式用实数二分来逼近就行了。
$$
\sum_{i=1}^k{\frac{m}{\left( 1+p \right) ^i}}=n\,\,\Rightarrow \sum_{i=1}^k{\frac{1}{\left( 1+p \right) ^i}}=\frac{n}{m}
$$
这个题还有一个坑就是利率不一定在100%以内...真是黑心银行
实数二分就随便写了,不用考虑什么边界条件
#include<iostream> #include<cmath> #include<cstdio> using namespace std; int n, m, k; double check(double p) { double sum = 0; for(int i = 1; i <= k; ++i) { sum += 1.0/pow(1 + p, i); } return sum; } int main() { cin >> n >> m >> k; double l = 0, r = 5.0; while((r - l) > 0.0000001) { double mid = (l + r) / 2; if(check(mid) <= (1.0 * n) / m) { r = mid; } else { l = mid; } } printf("%.1lf", l * 100); return 0; }
Comments NOTHING