其他分享
首页 > 其他分享> > Adva::0x04

Adva::0x04

作者:互联网

Adva::0x04 二分

Part 1. 引言

二分法是一种随处可见却非常精妙的算法,基础用法是在单调序列 / 函数中进行查找。当问题的答案具有单调性时,我们可以通过二分将求解转化为判定。

据说,只有 \(10\%\) 的程序员能写对二分(?)。二分的方法多种多样,需要注意边界,取舍,精度等。

Part 2. 二分基本模板

  1. 最大值最小化
while (l < r) {
  int mid = (l + r) >> 1;
  if (check(mid)) {
    r = mid;
  } else {
    l = mid + 1;
  }
}
  1. 最小值最大化
while (l < r) {
  int mid = (l + r + 1) >> 1;
  if (check(mid)) {
    l = mid;
  } else {
    r = mid - 1;
  }
}

我们考虑第一种方法无解当 \(mid = r\);第二种方法无解当 \(mid = l\)。我们可以利用这一性质处理无解的情况。

  1. 实数域二分
while (l + eps < r) {
  double mid = (l + r) / 2;
  if (check(mid)) {
    r = mid;
  } else {
    l = mid;
  }
}

另外,这种方法有时使用下面的写法精度会更高:

for (int i = 0; i < 100; ++i) {
  double mid = (l + r) / 2;
  if (check(mid)) {
    r = mid;
  } else {
    l = mid;
  }
}

Part 3. 二分答案转化为判定

我们在处理算法问题时,常遇到这种问题:对于最优解 \(S\),在 \(S\) 的一侧均不合法,而在另一侧均合法。形式化地说:若评分越高越优,则 \(\forall x > S\),都不合法,而 \(\forall x \leq S\),都有方案使得存在合法方案 \(x_0 \geq x\)。

例题

有 \(n\) 本书排成一行,厚度为 \(a_1 \sim a_n\)。

把它们分成连续 \(m\) 组,使最大组厚度之和最小。

我们二分最大厚度之和,每次 check 判断能否被分为最多 \(m\) 组。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <stack>
#include <cstring>
#include <climits>
using namespace std;
const int kMaxN = 1e5 + 5;
int n, m, a[kMaxN], r;
bool check(int t) {
    int groups = 0, sum = 0;
    for (int i = 1; i <= n; ++i) {
        if (sum + a[i] <= t) sum += a[i];
        else ++groups, sum = a[i];
    }
    return groups < m;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        r += a[i];
    }
    int l = 0;
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
    return 0;
}

Best Cow Fences

给定正整数数列 \(A\),求一个平均数最大的,长度不小于 \(L\) 的子段。

二分答案。判定:是否存在一个长度不小于 \(L\) 的子段,平均数不小于二分的值。

一个小 trick:每个数减去二分的值,然后判定有无非负子段和。

我们考虑问题“求一个长度不小于 \(L\) 的子段最大和”。

\[\max_{i - j \geq L}\left\{\sum{a_k}\right\} = \max_{L \leq i \leq n}\left\{sum_i - \min_{0 \leq j \leq i - L}\left\{sum_j\right\}\right\} \]

故我们维护前缀和 \(sum\) 的前缀最小值即可。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <stack>
#include <cstring>
#include <climits>
using namespace std;
const int kMaxN = 1e5 + 5;
int n, f, a[kMaxN];
double b[kMaxN], sum[kMaxN];
bool check(double x) {
	for (int i = 1; i <= n; ++i) {
		b[i] = a[i] - x;
	}
	for (int i = 1; i <= n; ++i) {
		sum[i] = sum[i - 1] + b[i];
	}
	double ans = -1e10, minval = 1e10;
	for (int i = f; i <= n; ++i) {
		minval = min(minval, sum[i - f]);
		ans = max(ans, sum[i] - minval);
	}
	return ans >= 0;
}
int main() {
	cin >> n >> f;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	double eps = 1e-6, l = -1e6, r = 1e6;
	while (l + eps < r) {
		double mid = (l + r) / 2;
		if (check(mid)) {
			l = mid; 
		} else {
			r = mid;
		}
	}
	cout << int(1000 * r) << endl;
	return 0;
}

Innovative Business

有 \(n\) 个元素,每一对元素之间的大小关系是确定的。你必须通过不超过 \(10000\) 次询问,每次询问得知两个数之间的大小关系,来把这 \(n\) 个数排好序。

我们考虑已经将 \(k - 1\) 个元素排好序,插入第 \(k\) 个元素。

于是我们二分 \(a_k\) 的位置,所有共 \(n \times \log_2n\) 次询问。

// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.

class Solution {
public:
    vector<int> specialSort(int N) {
        vector<int> v; v.push_back(1);
        for (int i = 2; i <= N; ++i) {
            int l = 0, r = v.size() - 1;
            while (l <= r) {
                int mid = l + r >> 1;
                if (compare(v[mid], i)) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
            v.push_back(i);
            for (int j = v.size() - 2; j > r; --j) swap(v[j], v[j + 1]);
        }
        return v;
    }
};

标签:二分,0x04,Adva,mid,else,int,include,check
来源: https://www.cnblogs.com/reliauk/p/0x04.html