二分答案三连发
LA3971 Assemble
你有b块钱,给出n个配件的个子的种类,性能和价格,每种类型的配件买一个,价格不超过b,因为有水桶效应,所以电脑的性能取决于性能最低的配件的性能,问你b块钱配的电脑性能最高有多少。
按照白书的说法,最大值尽量小,最小值尽量大之类的问题一般都可以用二分答案的方法来结局,这道题就是一道典型的最小值最大问题,所以采用二分答案
#include <cstdio> #include <cstring> #include <climits> #include <algorithm> #include <map> #include <vector> #include <string> #include <iostream> using namespace std; const int maxn = 1005; struct Component { int price,quality; }; int cnt,budget,maxquality; map<string,int> id; vector<Component> comp[maxn]; void readin() { char name[30],power[30]; int p,q,n; cnt = maxquality = 0; scanf("%d%d\n",&n,&budget); id.clear(); for(int i = 0;comp[i].size();i++) { comp[i].clear(); } for(int i = 0;i < n;i++) { scanf("%s%s%d%d",name,power,&p,&q); int nowid; if(id.count(name)) { nowid = id[name]; } else { nowid = cnt++; id[name] = nowid; } comp[nowid].push_back((Component){p,q}); if(q > maxquality) { maxquality = q; } } } bool ok(int val) { int total = 0; for(int i = 0;i < cnt;i++){ int cheapest = INT_MAX,m = comp[i].size(); for(int j = 0;j < m;j++) if(comp[i][j].quality >= val) { cheapest = min(cheapest,comp[i][j].price); } if(cheapest == INT_MAX) return false; total += cheapest; if(total > budget) { return false; } } return true; } void work() { int str = 0,end = maxquality,mid; while(str < end) { mid = str + (end - str + 1) / 2; if(ok(mid)) { str = mid; } else { end = mid - 1; } } cout << str << endl; } int main() { int T; cin >> T; while(T--) { readin(); work(); } return 0; }
LA3635
你请来了F个朋友,一起来分N个圆形的派,每个人得到的必须是一块或者是一块派的一部分,而不是几块派拼在一起的,问你每个人最多能得到多大的派
对浮点数的二分,设一个eps,当end-str<eps的时候跳出循环
PS.常量π的值可以用const int PI = acos(-1.0)来得,这样精度比较靠谱
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <cmath> using namespace std; const int maxn = 10005; const double PI = acos(-1.0); const double eps = 1e-5; int R[maxn]; inline double square(int R) { return PI * R * R; } int main() { int T,N,F; scanf("%d",&T); while(T--) { scanf("%d%d",&N,&F); int maxR = 0; for(int i = 0;i < N;i++) { scanf("%d",&R[i]); if(R[i] > maxR) { maxR = R[i]; } } double str = 0,end = square(maxR); while(end - str > eps) { double mid = (str + end) / 2; int count = 0; for(int i = 0;i < N;i++) { count += (int)(square(R[i]) / mid); if(count >= F + 1) { break; } } if(count >= F + 1) { str = mid; } else { end = mid; } } printf("%.4lf\n",str); } return 0; }
LA 3177 Beijing Guards
有n个人围城一个圈,每个人想拿ri种物品,要求相邻的两个人不能拿一样的,问最少需要多少种物品
当n为偶数的时候非常好处理,只要找到最大的ri+ri-1的和就行了。
当n为奇数的时候可以对所需要的物品数进行二分。
第一个人需要r1件物品,设left[i],right[i]分别为第i个人从1~ri,ri+1~p中拿的物品数目,偶数的人尽量拿前面的,奇数的人尽量拿后面的,那么第n个人必定是尽量拿后面的,此时判断时候和r1冲突即可
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100001; int r[maxn],n,sum,left[maxn],right[maxn]; bool ok(int val) { right[0] = 0; left[0] = r[0]; for(int i = 1;i < n;i++) { if(r[i] > val - left[i - 1] - right[i - 1]) return false; if((i + 1) % 2 == 0) { left[i] = min(r[i],r[0] - left[i - 1]); right[i] = r[i] - left[i]; } else { right[i] = min(r[i],val - r[0] - right[i - 1]); left[i] = r[i] - right[i]; } } return left[n - 1] == 0; } int bsearch() { int L = 1,R = sum; while(L < R) { int mid = (L + R) / 2; if(ok(mid)) R = mid; else L = mid + 1; } return L; } int main() { while(scanf("%d",&n),n) { sum = 0; for(int i = 0;i < n;i++) { scanf("%d",&r[i]); sum += r[i]; } if(n % 2 == 1) printf("%d\n",bsearch()); else { int maxd = 0; for(int i = 0;i < n;i++) { int f = ((i == 0) ? n - 1 : i - 1); maxd = max(maxd,r[i] + r[f]); } printf("%d\n",maxd); } } return 0; }
关于二分查找的一些总结:
很多问题都可以用二分来解决,但是根据每次寻找的条件不同,迭代的时候值的变化也会不同,简单总结一下;
1、查找某个元素是否存在
mid = (begin + end) / 2
if(mid < v) begin = mid + 1;
if(mid > v) end = mid – 1;
if(mid == v) return v;
2、查找不小于v的最小值
mid = (end + begin) / 2
if(mid < v) begin = mid + 1; else end = mid;
3、查找大于v的最小值
mid = (end + begin) / 2
if(mid <= v) begin = mid + 1; else end = mid;
3、查找满足条件的v的最小值
mid = (begin + end) / 2
if(ok(mid)) end = mid; else begin = mid – 1;
4、查找满足条件的v的最大值
mid = begin + (end – begin +1) / 2
if(ok(mid)) begin = mid; else end = mid – 1;
可以发现在寻找满足条件的v的最大值的时候mid取值和其他时候不一样,因为在处理两个相邻的数字的时候优先尝试大的那个,mid这样取可以保证一定比begin大,不然有可能会进入死循环
这里顺便总结一下STL中lower_bound和upper_bound 的用法
原型如下
template <class ForwardIterator, class T> ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val); template <class ForwardIterator, class T, class Compare> ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp); template <class ForwardIterator, class T> ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val); template <class ForwardIterator, class T, class Compare> ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
函数有两种重载,区别就是多了一个最后的比较函数,默认使用类的operator<,也可以自定义cmp函数,函数接受两个迭代器,分别表示一段范围,注意last表示的是最后一个元素的后一个,返回一个指向所需元素的迭代器,失败了返回last