假如我们遇到了这样的问题,问题要求我们在 y=f(x),(a≤x≤b) 中寻找符合条件的 y ,其中 f 可以为显式函数或隐式关系。一种朴素的思路是在取值范围内枚举所有的 x ,然后判断 y 是否满足条件。这样的时间复杂度是 O(b−a) 。但是当 b−a 的值很大时(例如 107 )就太耗时了。此时若 y 能满足“二分性”(例如升序,降序),则每次在怀疑区间 [l,r] 内取中点 xmid (初始时 l=a,r=b ),再通过判断 f(xmid) 是否满足条件来修正(缩小)怀疑区间(更新 l 或 r )。这样就可以将时间复杂度优化到 O(log(b−a)) 。
POJ 2456
大意
一维坐标轴上有 n 个位置,我们要把
思路
- 首先,在 k 为定值的情况下,判断在
d[s]=k 的情况下是否存在 s 是比较容易的(我们可以尝试在某个位置上放牛,同时判断在这个位置上放牛是否与前一只被放置的牛冲突,若不冲突的话就可以放,否则不行)。那么我们就可以枚举每个k ,在判断是否存在 s 的情况下更新最优解。 - 其次,如果真的枚举的话时间复杂度太大了。实际上,我们没有好好利用
k 的“二分性”。对于给定的k_0,如果存在对应的情况 s 的话,说明现在至少知道k 是满足条件的,所以小于 k 的最小距离就不用管了。如果不存在对应的情况s 的话,大于 k 的最小距离也不用管了。 - 最后,我们对怀疑区间
[1,1,000,000,000] 进行二分查找,查找出最优解即可。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 10, INF = 1e9;
int n, c, l, r, mid, x[maxn];
bool ok(int d) {
int cnt = 1, last = 0;
for(int i = 1; i < n; i++) {
if(x[i] - x[last] >= d) {
last = i;
cnt++;
}
}
return cnt >= c;
}
int main() {
scanf("%d%d", &n, &c);
for(int i = 0; i < n; i++) {
scanf("%d", &x[i]);
}
sort(x, x + n);
l = 1;
r = INF + 1;
while(r - l > 1) {
mid = (l + r) >> 1;
if(ok(mid)) {
l = mid;
}
else {
r = mid;
}
}
printf("%d\n", l);
return 0;
}
POJ 1064
大意
有 n 根长度各异的电缆,现要把这些电缆切割成等长的
思路
- 每种切割方案对应一个 d ,因此我们可以枚举
d ,判断切割方案是否合法的同时更新最优值。 - 但是这样做枚举量太大了。我们发现 d 是呈现“二分性”的,也就是对于
d0 ,知道了 d0 符合条件后任意小于或等于 d0 的 d 都满足条件。知道了d0 不满足条件后,任意大于 d0 的 d 都是不满足条件的。 - 于是我们对解区间进行二分查找,找到最优的
d 。
代码
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int INF = 1e9 + 10;
const int maxn = 1e4 + 10;
int n, k;
double l, r, m, mid, c[maxn];
bool ok(double x) {
int cnt = 0;
for(int i = 0; i < n; i++) {
cnt += (int)(c[i] / x);
}
return cnt >= k;
}
int main() {
scanf("%d%d", &n, &k);
for(int i = 0; i < n; i++) {
scanf("%lf", &c[i]);
}
sort(c, c + n);
l = 0;
r = INF;
for(int i = 0; i < 100; i++) {
mid = (l + r) / 2;
if(ok(mid)) {
l = mid;
}
else {
r = mid;
}
}
printf("%.2f\n", floor(100 * l) / 100);
return 0;
}
POJ 3104
大意
思路
代码
POJ 3111
大意
思路
代码
POJ 3579
大意
思路
代码
Codeforces 689C
大意
小偷们有一个容量为 n 背包,他们偷窃的
思路
- 从 m 得知
n 是困难的,但是从 n 得知m 却是可行的。根据计数方法,将 n 代入下面的c 函数中就能求出 m 。于是我们可以枚举所有可能的n ,当结果等于 m 的时候用当前n 更新最优解(此处是最小值)就好了。 - 但是枚举的话太费时。因此我们利用 m 的“二分性”(
m 这里是对 n 的升序)来对n 进行二分查找,就能在短时间内找到答案。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m, l, r, mid;
ll c(ll n) {
ll res = 0;
for(ll q = 2; n >= q * q * q; q++) {
res += n / (q * q * q);
}
return res;
}
int main() {
cin >> m;
l = 7;
r = 100 * m;
while(r - l > 1) {
mid = (l + r) >> 1;
if(c(mid) >= m) {
r = mid;
}
else {
l = mid;
}
}
if(c(r) == m) {
cout << r << endl;
}
else {
cout << -1 << endl;
}
return 0;
}
Codeforces 689D
大意
题目给出两个数组 a 和
思路
- 我们定义 max(i,j) 为 a[i] 到 a[j] 之间的最大值。那么 max 函数有这样的性质:当 i 为定值的时候,
max(i,j) 随着 j 的增大而非减。同样,min(i,j) 随着j的增大而非增。也就是说 max(i,j)−min(i,j) 在 i 为定值的情况下对于j 是非减函数。有了“序”以后一切都好办了。我们相当于找到了 max(i,j)−min(i,j) 的二分性。于是对于每个 i ,我们可以枚举j ,找出满足 max(i,j)−min(i,j) 的连续的 j1,...,jk ,那么对于当前的 i 而言,满足max(i,j)−min(i,j) 的 j 的个数就是jk−j1+1 ( j 的合法区间的长度),我们将其加到答案ans 中。枚举完 i 以后,ans 就是最终答案了。 - 其中,根据二分性我们将对于 j 的枚举改成对于
j 的二分查找能够将时间复杂度控制在合理的范围内。另外快速查询 max(i,j) 和 min(i,j) 可以用 ST 表(查询复杂度 O(1) )或线段树(查询复杂度 O(logn) )这样的数据结构,否则仍然会超时。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int n, lb, ub, a[maxn], b[maxn];
ll ans;
struct table {
int stTable[maxn][32][2];
int preLog2[maxn];
void init(int n, int* array) {
preLog2[1] = 0;
for(int i = 2; i <= n; i++) {
preLog2[i] = preLog2[i-1];
if((1 << preLog2[i] + 1) == i) {
++preLog2[i];
}
}
for(int i = n - 1; i >= 0; i--) {
stTable[i][0][0] = stTable[i][0][1] = array[i];
for(int j = 1; (i + (1 << j) - 1) < n; j++) {
stTable[i][j][0] = max(stTable[i][j-1][0], stTable[i+(1<<j-1)][j-1][0]);
stTable[i][j][1] = min(stTable[i][j-1][1], stTable[i+(1<<j-1)][j-1][1]);
}
}
}
int rmq(int l, int r, int d) {
int len = r - l + 1, k = preLog2[len];
if(d == 0) {
return max(stTable[l][k][0], stTable[r-(1<<k)+1][k][0]);
}
else {
return min(stTable[l][k][1], stTable[r-(1<<k)+1][k][1]);
}
}
}A, B;
int binary(int L, int& lb, int& ub) {
int l = L - 1, r = n;
while(r - l > 1) {
int mid = (l + r) >> 1;
int tmp = A.rmq(L, mid, 0) - B.rmq(L, mid, 1);
if(tmp < 0) {
l = mid;
}
else {
r = mid;
}
}
lb = r;
l = L - 1, r = n;
while(r - l > 1) {
int mid = (l + r) >> 1;
int tmp = A.rmq(L, mid, 0) - B.rmq(L, mid, 1);
if(tmp <= 0) {
l = mid;
}
else {
r = mid;
}
}
ub = r;
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
A.init(n, a);
for(int i = 0; i < n; i++) {
scanf("%d", &b[i]);
}
B.init(n, b);
for(int L = 0; L < n; L++) {
binary(L, lb, ub);
ans += ub - lb;
}
printf("%I64d\n", ans);
return 0;
}