《挑战程序设计竞赛》课后练习题解集——3.1 不光是查找值!“二分搜索”

人盡茶涼 提交于 2020-02-01 22:13:07

不光是查找值!“二分搜索”

最大化最小值

POJ 3258  N块石子,要移去M块,求剩余石子之间距离的最小值的最大值

二分答案

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int a[50005];
 7 int l, m, n;
 8 
 9 bool C(int d) {
10     int last = 0, t = 0;
11     for (; last <= n;) {
12         int crt = last + 1;
13         while (crt <= n + 1 && a[crt] - a[last] < d) crt++, t++;
14         if (crt > n + 1 || t > m) return false;
15         last = crt;
16     }
17     return true;
18 }
19 
20 int main() {
21     scanf("%lld%d%d", &l, &n, &m);
22     for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
23     sort(a + 1, a + n + 1);
24     a[n + 1] = l;
25     int lb = 0, ub = l + 1;
26     while (ub - lb > 1) {
27         int mid = (ub + lb) / 2;
28         if (C(mid))
29             lb = mid;
30         else
31             ub = mid;
32     }
33     printf("%d", lb);
34 }
View Code

 

POJ 3273  给出N天的预算,要划分成M组,求所有组中的最大值的最小值

二分答案

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int a[100005];
 7 int n, m;
 8 
 9 bool C(int sum) {
10     int last = 0;
11     for (int i = 0; i < m; i++) {
12         int crt = last + 1, r = a[last];
13         while (crt < n && r + a[crt] <= sum) {
14             r += a[crt];
15             crt++;
16         }
17         if (crt == n) return true;
18         last = crt;
19     }
20     return false;
21 }
22 
23 int main() {
24     scanf("%d%d", &n, &m);
25     for (int i = 0; i < n; i++) scanf("%d", &a[i]);
26     int lb = *max_element(a, a + n) - 1, ub = (int)1e9;
27     while (ub - lb > 1) {
28         int mid = (lb + ub) / 2;
29         if (C(mid))
30             ub = mid;
31         else
32             lb = mid;
33     }
34     cout << ub;
35 }
View Code

 

POJ 3104  有一堆湿衣服,每秒蒸发1水分,还有一个蒸干器,每秒令一件衣服蒸发k水分,求所有衣服都干的最少时间

二分答案。注意用蒸发器时其实是加速蒸发k-1而不是k,要特判k=1避免除0 

 1 #include <cstdio>
 2 #include <cstdlib>
 3 using namespace std;
 4 #define ll long long
 5 
 6 const int maxn = 1e5 + 5;
 7 
 8 int a[maxn], n, k;
 9 
10 int max(int a, int b) { return a > b ? a : b; }
11 
12 bool C(int x) {
13     ll res = 0;
14     for (int i = 0; i < n; i++) res += (max(0, a[i] - x) + k - 2) / (k - 1);
15     return res <= x;
16 }
17 
18 int main() {
19     scanf("%d", &n);
20     for (int i = 0; i < n; i++) scanf("%d", &a[i]);
21     scanf("%d", &k);
22     if (k == 1) {
23         int res = 0;
24         for (int i = 0; i < n; i++) res = max(res, a[i]);
25         printf("%d\n", res);
26         exit(0);
27     }
28     int l = 0, r = (int)1e9 + 1;
29     while (r - l > 1) {
30         int mid = (l + r) / 2;
31         if (C(mid))
32             r = mid;
33         else
34             l = mid;
35     }
36     printf("%d\n", r);
37 }
View Code

 

POJ 3045  有N头奶牛,具有体重w和力量s,它们叠在一起,每一只奶牛的risk值为它上方奶牛w之和减去自身s;求所有risk的最大值的最小值

考虑risk = sum_w(上方)- s(自身)= sum_w(上方+自身) - w(自身)- s(自身)。sum_w(上方+自身) 在未确定剩余奶牛顺序时就是一个定值,所有w+s不大于sum_w - risk(假想的答案)的奶牛,假定有k头,可以以任意顺序放在最后一个,进而可以扩展到这k头可以以任意顺序放在最后k个位置;不妨以w+s从大到小顺序放置,之后sum_w的值减小,又有k2头奶牛w+s不大于sum_w2 - risk,如法炮制。所以,按w+s排序即可代表最优顺序

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstdlib>
 5 using namespace std;
 6 #define ll long long
 7 
 8 const int maxn = 5e4 + 5;
 9 int n;
10 
11 struct cow {
12     ll w, s;
13     bool operator<(const cow& o) const { return w + s > o.w + o.s; }
14 } a[maxn];
15 
16 int main() {
17     scanf("%d", &n);
18     for (int i = 0; i < n; i++) scanf("%lld %lld", &a[i].w, &a[i].s);
19     sort(a, a + n);
20     ll sum = 0, risk = (int)-1e9;
21     for (int i = 0; i < n; i++) sum += a[i].w;
22     for (int i = 0; i < n; i++) {
23         risk = max(risk, sum - a[i].w - a[i].s);
24         sum -= a[i].w;
25     }
26     printf("%lld\n", risk);
27 }
View Code

 

最大化平均值

POJ 2976  裸的01分数规划

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 #include <vector>
 7 using namespace std;
 8 #define ll long long
 9 const int N = 1e3 + 5;
10 
11 int n, k;
12 double a[N], b[N], c[N];
13 
14 bool C(double x) {
15     for (int i = 0; i < n; i++) {
16         c[i] = a[i] - b[i] * x;
17     }
18     sort(c, c + n, greater<double>());
19     double res = 0;
20     for (int i = n - k - 1; i >= 0; i--) {
21         res += c[i];
22     }
23     return res >= 0;
24 }
25 
26 int main() {
27     while (scanf("%d%d", &n, &k) != EOF, n) {
28         for (int i = 0; i < n; i++) scanf("%lf", &a[i]);
29         for (int i = 0; i < n; i++) scanf("%lf", &b[i]);
30         double l = 0, r = 1e12 + 1;
31         for (int i = 0; i < 60; i++) {
32             double mid = (l + r) / 2;
33             if (C(mid))
34                 l = mid;
35             else
36                 r = mid;
37         }
38         printf("%.0f\n", l * 100);
39     }
40 }
View Code

 

POJ 3111  同上

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 #include <vector>
 7 using namespace std;
 8 #define ll long long
 9 const int maxn = 1e5 + 5;
10 
11 int n, k;
12 double a[maxn], b[maxn];
13 
14 struct node {
15     double c;
16     int id;
17     bool operator<(const node &o) const { return c > o.c; }
18 } p[maxn];
19 
20 bool C(double x) {
21     for (int i = 0; i < n; i++) {
22         p[i].c = a[i] - b[i] * x;
23         p[i].id = i + 1;
24     }
25     sort(p, p + n);
26     double res = 0;
27     for (int i = 0; i < k; i++) res += p[i].c;
28     return res >= 0;
29 }
30 int main() {
31     scanf("%d %d", &n, &k);
32     for (int i = 0; i < n; i++) scanf("%lf %lf", &a[i], &b[i]);
33     double l = 0, r = 1e6 + 1;
34     for (int i = 0; i < 60; i++) {
35         double mid = (l + r) / 2;
36         if (C(mid)) {
37             l = mid;
38         } else
39             r = mid;
40     }
41     for (int i = 0; i < k; i++)
42         printf("%d%c", p[i].id, i == k - 1 ? '\n' : ' ');
43 }
View Code

 

查找第k大的值

POJ 3579  给出N个数,有C(n, 2)个差值,输出这些差值的中位数(偶数个时输出偏小的一个)

二分答案。这题貌似卡常,二分的右端点得结合实际数据a[n-1]-a[0],直接1e9会T

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 using namespace std;
 6 #define ll long long
 7 
 8 const int maxn = 1e5 + 5;
 9 ll a[maxn], m;
10 int n;
11 
12 bool C(int x) {
13     ll sum = 0;
14     for (int i = 0; i < n; i++) {
15         sum += upper_bound(a + i + 1, a + n, a[i] + x) - (a + i + 1);
16     }
17     return sum >= m;
18 }
19 
20 int main() {
21     while (scanf("%d", &n) != EOF) {
22         m = ((ll)n * (n - 1) / 2 + 1) / 2;
23         for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
24         sort(a, a + n);
25         int l = -1, r = a[n - 1] - a[0];
26         while (r - l > 1) {
27             int mid = l + (r - l) / 2;
28             if (C(mid)) {
29                 r = mid;
30             } else {
31                 l = mid;
32             }
33         }
34         printf("%d\n", r);
35     }
36 }
View Code

 

 POJ 3658  有一个N(<=5e4)阶矩阵,第i行第j列元素大小为 i2 + 100000 × i + j2 - 100000 × j + i × j,求第M小的数

好题目。j 固定时式子随 i 递增,所有二分judge时可以枚举 j ,再次二分得出这一列有多少个不大于x的数

事实上矩阵在这题没有起到作用,是在提示我们考虑固定i或者j来考虑

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 using namespace std;
 6 #define ll long long
 7 
 8 ll n, m;
 9 
10 int c1(ll x, ll b, ll c) {
11     if (1 + b + c > x) return 0;
12     int l = 0, r = n, ans;
13     while (r >= l) {
14         ll mid = (l + r) / 2;
15         if (mid * mid + b * mid + c <= x) {
16             l = mid + 1;
17             ans = mid;
18         } else
19             r = mid - 1;
20     }
21     return ans;
22 }
23 
24 bool c2(ll x) {
25     ll sum = 0;
26     for (ll i = 1; i <= n; i++) sum += c1(x, 100000 + i, i * i - 100000 * i);
27     return sum >= m;
28 }
29 
30 int t;
31 int main() {
32     scanf("%d", &t);
33     while (t--) {
34         scanf("%lld %lld", &n, &m);
35         ll l = (ll)-3e9, r = (ll)8e9, ans;
36         while (r >= l) {
37             ll mid = l + (r - l) / 2;
38             if (c2(mid)) {
39                 r = mid - 1;
40                 ans = mid;
41             } else
42                 l = mid + 1;
43         }
44         printf("%lld\n", ans);
45     }
46 }
View Code

 

 最小化第k大的值

POJ 2010  C个cow,有分数和学校要给它们的补贴,选取N(奇数)个,使得总补贴不超过F且分数的中位数尽可能大。在2.4节已经出现过这题了

按分数排序。考虑下标id,其意义是 分数前一半(含中位数)的下标都大于等于id,后一部分是从未选cow中按补贴选(可能分数也大于中位数),注意 下标恰好为id的cow不一定选!不这样选是没有单调性的,无法二分

这题其实没有二分的必要,judge就已经是nlogn的,而原先用堆维护答案扫一遍过去也是nlogn。事实上维护堆是200ms,二分是500ms

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 #include <vector>
 5 using namespace std;
 6 #define ll long long
 7 #define pii pair<int, int>
 8 #define fi first
 9 #define se second
10 #define pb push_back
11 
12 const int maxn = 1e5 + 5;
13 
14 pii cow[maxn];
15 int t[maxn];
16 int n, c, f;
17 
18 inline bool C(int id) {
19     ll sum = 0;
20     for (int i = id; i < c; i++) t[i] = cow[i].se;
21     sort(t + id, t + c);
22     for (int i = id; i <= id + n / 2; i++) sum += t[i];
23     vector<int> vec;
24     for (int i = id + n / 2 + 1; i < c; i++) vec.push_back(t[i]);
25     for (int i = 0; i < id; i++) vec.push_back(cow[i].se);
26     sort(vec.begin(), vec.end());
27     for (int i = 0; i < n / 2; i++) sum += vec[i];
28     //  printf("%d %d\n", id, sum);
29     return sum <= f;
30 }
31 
32 int main() {
33     scanf("%d %d %d", &n, &c, &f);
34     for (int i = 0; i < c; i++) scanf("%d %d", &cow[i].fi, &cow[i].se);
35     sort(cow, cow + c);
36 
37     int l = n / 2, r = c - n / 2 - 1, ans = -1;
38     while (r >= l) {
39         int mid = (l + r) / 2;
40         if (C(mid)) {
41             l = mid + 1;
42             ans = mid;
43         } else
44             r = mid - 1;
45     }
46 
47     printf("%d\n", ans == -1 ? -1 : cow[ans].first);
48 }
View Code

 

 POJ 3662  给个电线连接图,供电公司可以免费提供k条电线,自己需要支付的费用是剩余电线中最长的长度。要使节点1到N联通,求最小费用

二分最小费用,小于等于x的边全选,长度视为0;否则为1,最后d[N]就代表需要供电公司提供的电线数量,与k比较

这一题的条件可以更隐晦成,“使一条路径上第k+1大的线段最小”

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 #include <queue>
 6 #include <vector>
 7 using namespace std;
 8 #define ll long long
 9 #define pii pair<int, int>
10 #define fi first
11 #define se second
12 #define pb push_back
13 
14 const int maxn = 1e3 + 5;
15 const int inf = 0x3f3f3f3f;
16 int n, m, u, v, x, k;
17 
18 struct edge {
19     int to, cost;
20 };
21 vector<edge> g[1005];
22 int d[maxn], vis[maxn];
23 
24 bool C(int x) {
25     for (int i = 1; i <= n; i++) {
26         d[i] = inf;
27         vis[i] = 0;
28     }
29     priority_queue<pii, vector<pii>, greater<pii> > que;
30     d[1] = 0;
31     que.push(pii(0, 1));
32     while (!que.empty()) {
33         pii p = que.top();
34         que.pop();
35         int v = p.se;
36         if (d[v] < p.fi) continue;
37         vis[v] = 1;
38         for (int i = 0; i < g[v].size(); i++) {
39             edge e = g[v][i];
40             int l = e.cost <= x ? 0 : 1;
41             if (!vis[e.to] && d[e.to] > l + d[v]) {
42                 d[e.to] = l + d[v];
43                 que.push(pii(d[e.to], e.to));
44             }
45         }
46     }
47     return d[n] <= k;
48 }
49 
50 int main() {
51     scanf("%d %d %d", &n, &m, &k);
52     for (int i = 0; i < m; i++) {
53         scanf("%d %d %d", &u, &v, &x);
54         g[u].pb(edge{v, x});
55         g[v].pb(edge{u, x});
56     }
57     int l = 0, r = (int)1e6, ans = -1;
58     while (r >= l) {
59         int mid = (l + r) / 2;
60         if (C(mid)) {
61             r = mid - 1;
62             ans = mid;
63         } else
64             l = mid + 1;
65     }
66     printf("%d\n", ans);
67 }
View Code

 

 其他

POJ 1759  有一条受重力影响的具有N个节点的线,除了最左右的节点 每个节点的高度为左右节点的高度的平均值-1,给出左节点的高度,求右节点的最低高度,使得所有节点高度都非负

设第二个节点高度为a1,递推关系会发现B的高度与a1成正比,所以二分B点高度可以变为二分a1的高度

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <iostream>
 5 using namespace std;
 6 #define ll long long
 7 
 8 int n;
 9 double a;
10 
11 bool C(double x) {
12     double a0 = a, a1 = x, a2;
13     for (int i = 3; i <= n; i++) {
14         a2 = 2 * a1 - a0 + 2;
15         if (a2 < 0) return false;
16         if (a2 >= a1) return true;
17         a0 = a1, a1 = a2;
18     }
19     return true;
20 }
21 
22 int main() {
23     scanf("%d %lf", &n, &a);
24     double l = 0, r = a;
25     for (int i = 0; i < 1000; i++) {
26         double mid = (l + r) / 2;
27         if (C(mid))
28             r = mid;
29         else
30             l = mid;
31     }
32     double a0 = a, a1 = r, a2;
33     for (int i = 3; i <= n; i++) {
34         a2 = 2 * a1 - a0 + 2;
35         a0 = a1, a1 = a2;
36     }
37     printf("%.2f", a2);
38 }
View Code

 

 POJ 3484  给出n个数列,已知有至多一个数出现了奇数次,找出这个数及其出现次数

考虑前缀和,在出现这个“出现了奇数次的数”之前,所有数的个数是偶数,而在这个数之后,就变成了奇数;以此为依据就可以进行二分

这题输入也不容易,想着gets现在(2020年)已经不提倡用了,就把网上找的代码改成了用cin.getline读取

 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 using namespace std;
 7 #define ll long long
 8 
 9 const int maxn = 2e4 + 5;
10 
11 int a[maxn], b[maxn], d[maxn];
12 int top;
13 
14 char str[100];
15 
16 ll count(ll x) {
17     ll sum = 0;
18     for (int i = 0; i < top; i++) {
19         if (x >= b[i])
20             sum += (b[i] - a[i]) / d[i] + 1;
21         else if (x >= a[i])
22             sum += (x - a[i]) / d[i] + 1;
23     }
24     return sum;
25 }
26 
27 int main() {
28     ios::sync_with_stdio(false);
29     cin.tie(0);
30     while (cin.getline(str, 100)) {
31         top = a[0] = 0;
32         sscanf(str, "%lld %lld %lld", &a[top], &b[top], &d[top]);
33         top++;
34         if (a[0] == 0) continue;
35         while (cin.getline(str, 100) && strlen(str)) {
36             sscanf(str, "%lld %lld %lld", &a[top], &b[top], &d[top]);
37             top++;
38         }
39         ll l = 0, r = 1LL << 32;
40         while (r - l > 1) {
41             ll mid = l + (r - l) / 2;
42             if (count(mid) & 1)
43                 r = mid;
44             else
45                 l = mid;
46         }
47         if (r == 1LL << 32)
48             printf("no corruption\n");
49         else
50             printf("%lld %lld\n", r, count(r) - count(r - 1));
51     }
52 }
View Code

 

END

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!