789. 数的范围
整数二分 :
- 在一个区间上定义某种性质,使得这个性质在右半区间是满足的,在左半区间是不满足的,且这两个区间没有交点(因为是整数二分);这样二分就既可以找右半部分也可以左半部分的边界,找这两个点分别是两个不同的模版
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main() {
int n, q;
cin >> n >> q;
for (int i = 0; i < n; ++ i)
cin >> a[i];
while (q -- ) {
int x;
cin >> x;
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
if (a[r] != x) {
cout << "-1 -1" << endl;
} else {
cout << r << ' ';
l = 0, r = n - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (a[mid] <= x) {
l = mid;
} else {
r = mid - 1;
}
}
cout << r << endl;
}
}
}
790. 数的三次方根
#include <iostream>
using namespace std;
int main() {
double n;
cin >> n;
double l = -10000, r = 10000;
while (r - l > 1e-8) {
double mid = (l + r) / 2;
if (mid * mid * mid >= n) {
r = mid;
} else {
l = mid;
}
}
printf("%.6lf", l);
}
102. 最佳牛围栏
- 对于二分,二分是二分性而不是单调性 只要满足可以找到一个值一半满足一半不满足即可 而不用满足单调性;本题没有出现二分的特征词(最大值最小、最小值最大),所给数列也没有单调性,并且不适于排序,可以先提出假设这道题可以用二分解出。
- 假设最优解为x,如果当前需要判断是否存在一段的平均值大于等于mid,如果mid ≤ \leq ≤x,必然存在一段平均值大于等于mid的(比如x);如果mid > > >x,必然不存在一段平均值大于等于mid的,因此证明了这道题的二分性,可以用二分解决问题
- 我们要找一段 连续的且区间长度不小于m且区间的平均值大于等于mid的区间,优化为区间中每个数减去mid,利用前缀和得到的区间和大于等于0,这样可以省去求平均值的步骤
- 由于区间长度只有下限的限制,长度并不固定,即使用前缀和优化,仍然需要 n 2 n^2 n2求和。我们使用双指针, i = 0 , j = m i=0,j=m i=0,j=m,每次i++,j++,这样i与j之间的差距固定为m+1,且 s [ j ] − s [ i ] s[j]-s[i] s[j]−s[i]为i+1~j这一区间的和用一个变量minv维护i遍历过程中的前缀和最小值,这样比较的距离就一定是 ≥ \geq ≥m的了,且为 O ( N ) O(N) O(N)
- 因为我们找的极大值 所以要右端点*1000 否则可能会出错
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N];
double s[N];
bool check(double x) {
for (int i = 1; i <= n; ++ i) {
s[i] = s[i - 1] + (a[i] - x);
}
double minv = 1e9;
for (int i = 0, j = m; j <= n; ++ i, ++ j) {
minv = min(minv, s[i]);
if (s[j] - minv >= 0) {
return true;
}
}
return false;
}
int main() {
cin >> n >> m;
double l = 0, r = 0;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
r = max(r, (double)a[i]);
}
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
cout << (int)(r * 1000);
}
113. 特殊排序
- 本题与一般排序有三个区别:
- 1.这是交互题,不能用><,只能调用compare接口询问
- 2.大小不具有传递性,a<b,b<c,不能推出a<c
- 3.不能超过一万次询问,n为1000,nlogn略小于一万
- 第二个性质仅仅导致结果不唯一,题目只要求输出其中一种,因此可以忽略
// 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> res;
res.push_back(1);
for (int i = 2; i <= N; ++ i) {
int l = -1, r = i - 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (compare(res[mid], i)) {
l = mid;
} else {
r = mid;
}
// [l] < i
}
res.push_back(i);
for (int j = res.size() - 1; j >= l + 2; -- j) {
swap(res[j], res[j - 1]);
}
}
return res;
}
};
P2249 【深基13.例1】查找
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int main() {
int n, q;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);
while (q -- ) {
int x;
scanf("%d", &x);
int l = 1, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
if (a[l] != x) cout << -1 << ' ';
else cout << l << ' ';
}
}
P1102 A-B 数对
- 找等于一个数的数量:用大于的位置减去大于等于的位置
- 找位置,既可以用库函数二分,也可以自己写二分,还可以用双指针,因为排序后遍历找大于每个数的位置必然是不变或者向右的
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll a[N];
map<ll, ll> ma;
int main() {
int n;
ll c;
cin >> n >> c;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
ma[a[i]] ++ ;
}
ll ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += ma[a[i] - c];
}
cout << ans;
}
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N];
int main() {
int n;
ll c;
cin >> n >> c;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
sort(a + 1, a + n + 1);
ll ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += (upper_bound(a + 1, a + n + 1, a[i] - c) - lower_bound(a + 1, a + n + 1, a[i] - c));
}
cout << ans;
}
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n;
ll c;
int a[N];
int upper(ll x) {
int l = 0, r = n + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (a[mid] <= x) {
l = mid;
} else {
r = mid;
}
}
return r;
}
int lower(ll x) {
int l = 0, r = n + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (a[mid] < x) {
l = mid;
} else {
r = mid;
}
}
return r;
}
int main() {
cin >> n >> c;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
sort(a + 1, a + n + 1);
ll ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += (upper(a[i] - c) - lower(a[i] - c));
}
cout << ans;
}
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll a[N];
int main() {
int n;
ll c;
cin >> n >> c;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
sort(a + 1, a + n + 1);
ll ans = 0;
int r1 = 1, r2 = 1;
for (int i = 1; i <= n; ++ i) {
while (r1 <= n && a[r1] - a[i] < c) r1 ++ ;
while (r2 <= n && a[r2] - a[i] <= c) r2 ++ ;
if (a[r1] - a[i] == c && a[r2 - 1] - a[i] == c && r1 >= i + 1)
ans += r2 - r1;
}
cout << ans;
}
P1873 [COCI 2011/2012 #5] EKO / 砍树
- 答案h最高高度越大,木材总长度越小,满足二分,临界点就是木材总长度大于等于m(l)
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m;
int a[N];
bool check(int x) {
ll ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += (a[i] - min(a[i], x));
}
if (ans >= m) return true;
return false;
}
int main() {
cin >> n >> m;
int mx = 0;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
mx = max(mx, a[i]);
}
int l = -1, r = mx + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
cout << l;
}
P1024 [NOIP2001 提高组] 一元三次方程求解
- 根据题意,每个根之间绝对值大于等于1,说明每一个长度为1的区间内最多有两个根
- 在题目所给的范围内,以长度为1枚举每一个区间,如果两个端点之积小于等于0,说明之间有一个根,使用二分法找根;特别地,如果左端点的函数值等于0,说明这个左端点就是一个根
#include <iostream>
using namespace std;
double a, b, c, d;
double func(double x) {
return a * x * x * x + b * x * x + c * x + d;
}
int main() {
scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
int cnt = 0;
for (int i = -100; i < 100; ++ i) {
double l = i, r = i + 1;
double y1 = func(l), y2 = func(r);
if (!y1) {
printf("%.2lf ", l);
cnt ++ ;
}
if (y1 * y2 < 0) {
while (r - l >= 1e-8) {
double mid = (l + r) / 2;
if (func(mid) * func(r) < 0) {
l = mid;
} else {
r = mid;
}
}
printf("%.2lf ", r);
cnt ++ ;
}
if (cnt == 3) break;
}
}
P1678 烦恼的高考志愿
- 要注意没有小于它的数和没有大于等于它的数 的情况
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[N];
int main() {
int m, n;
cin >> m >> n;
for (int i = 0; i < m; ++ i)
cin >> a[i];
sort(a, a + m);
ll ans = 0;
while (n -- ) {
ll x;
cin >> x;
int l = -1, r = m;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (a[mid] >= x) {
r = mid;
} else {
l = mid;
}
}
// ans += min(abs(a[l] - x), abs(a[r] - x));
if (l == -1) {
ans += abs(a[0] - x);
} else if (r == m) {
ans += abs(a[m - 1] - x);
} else {
ans += min(abs(a[l] - x), abs(a[r] - x));
}
}
cout << ans;
}
P2440 木材加工
- 长度l越大,能得到的小段的数量越小,临界点为小段的数量大于等于k(l)
- 注意这里边界l必须是0,不能是-1,否则会RE
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, k;
int a[N];
bool check(int x) {
int ans = 0;
for (int i = 0; i < n; ++ i) {
ans += a[i] / x;
}
if (ans >= k) return true;
return false;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; ++ i)
cin >> a[i];
int l = 0, r = 1e8 + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
// if (l == -1) cout << 0;
// else cout << l;
cout << l;
}
P2678 [NOIP2015 提高组] 跳石头
- 最短跳跃距离越大,需要移走的石头数量越大,临界点是移走小于等于m块石头
- 二分最短跳跃距离,判断需要移走的石头数量
- 最短跳跃距离左边界为输入的最小距离,右边界为之和
- 判断当前这个最短距离是否可以满足移走石头数量小于等于m时,需要一个变量记录当前这一段的起点,然后动态更新起点和需要移走的石头数量
#include <iostream>
using namespace std;
const int N = 5e4 + 10;
int len, n, k;
int a[N];
bool check(int m) {
int cnt = 0, last = 0;
for (int i = 1; i <= n; ++ i) {
if (a[i] - a[last] < m) {
cnt ++ ;
} else {
last = i;
}
}
if (len - a[last] < m) cnt ++ ;
return (cnt <= k);
}
int main() {
cin >> len >> n >> k;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
int l = 0, r = len + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
cout << l;
}
P3853 [TJOI2007]路标设置
- 最大距离越小,需要增加的路标数量越大,临界点为增加的数量小于等于k
- 判断当前的最大距离是否满足增加路标数量小于等于k时,遍历每一个原先的路标,如果它与上一个之间的距离大于最大距离,说明要加这么多个路标在它们之间
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int len, n, k;
int a[N];
bool check(int m) {
int cnt = 0;
for (int i = 2; i <= n; ++ i) {
if (a[i] - a[i - 1] > m) {
cnt += (a[i] - a[i - 1]) / m;
if ((a[i] - a[i - 1]) % m == 0) cnt -- ;
}
}
return (cnt <= k);
}
int main() {
cin >> len >> n >> k;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
int l = 0, r = len + 1;
while (l + 1 != r) {
int mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid;
}
}
cout << r;
}
P1182 数列分段 Section II
- 每段之和最大值越小,要划分成的段数越大,临界点是划分段数小于等于m段
- 判断时,首先判断加上是否会超出,然后直接加上
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const ll inf = 1e9;
int n, m;
ll a[N];
bool check(ll x) {
ll cnt = 1, now = 0;
for (int i = 0; i < n; ++ i) {
if (now + a[i] > x) {
cnt ++ ;
now = 0;
}
now += a[i];
}
return cnt <= m;
}
int main() {
cin >> n >> m;
ll mx = 0, sum = 0;
for (int i = 0; i < n; ++ i) {
cin >> a[i];
mx = max(mx, a[i]);
sum += a[i];
}
ll l = mx - 1, r = min(sum, inf) + 1;
while (l + 1 != r) {
ll mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid;
}
}
cout << r;
}
P1163 银行贷款
- ∑ i = 1 k m ( 1 + p ) i = n \sum\limits_{i=1}^k{\frac{m}{(1+p)^i}}=n i=1∑k(1+p)im=n
- 化简得 ∑ i = 1 k 1 ( 1 + p ) i = n m \sum\limits_{i=1}^k{\frac{1}{(1+p)^i}}=\frac{n}{m} i=1∑k(1+p)i1=mn,由于nmk为常数,因此左边的结果是一个一元单调函数,右边是固定值,因此可以二分;且p越大,左边的结果越小
- 根据等比数列求和公式, 1 − ( 1 1 + p ) k p = n m \frac{1-(\frac{1}{1+p})^k}{p}=\frac{n}{m} p1−(1+p1)k=mn
#include <iostream>
#include <cmath>
using namespace std;
int n, m, k;
bool check(double p) {
double div = 1 - pow(1 / (1 + p), k);
return (div / p >= n * 1.0 / m);
}
int main() {
cin >> n >> m >> k;
double l = 0, r = 100;
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
printf("%.1lf", l * 100);
}
P3743 kotori的设备
- 精读不是越小越好,因为会TLE;这里1e-4的精读要求为1e-6即可满足(一般高两个数量级)
- double 上界可以有 1 0 11 10^{11} 1011
- 这里之所以将p和sum弄成double,是因为它们超出了int范围
- 与其纠结于时间,将所有设备需要的总能量加起来,与总共能提供的能量做比较即可
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
double p;
int a[N], b[N];
bool check(double x) {
double sum = 0;
for (int i = 1; i <= n; ++ i) {
if (a[i] * x > b[i]) {
sum += (a[i] * x - b[i]);
}
}
return (sum <= p * x);
}
int main() {
cin >> n >> p;
double sum = 0;
for (int i = 1; i <= n; ++ i) {
cin >> a[i] >> b[i];
sum += a[i];
}
if (sum <= p) {
cout << -1.000000;
return 0;
}
double l = 0, r = 1e11;
while (r - l > 1e-6) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
cout << l;
}