二分
二分
二分一般再应用时分为整数二分和实数二分。整数域上的二分要注意边界条件、左右区间的开闭情况,避免遗漏答案或者陷入死循环;实数域上的二分要注意精度问题。
二分答案
题目的答案具备单调性,可以在某个区间里利用二分求解
注意最终答案的范围,需要仔细确定一下!!!
整数二分
两个经典模型:最大值最小化 和 最小值最大化
最大值最小化
题意
给定 n 个顶点,m条边的无向图,每个点都有点权 \(f_i\),边均有边权。求起点 1 到终点 N 的所有可能路径中,在路径总边权不超过 b 的情况下,路径上最大点权(路径上的点中的最大点权)的最小值
思路
可以对点权进行二分,之后判断仅利用不大于给定点权的点跑最短路是否符合(如果最短路的总边权都大于 b,那么其他路径也都大于 b)
“仅利用不大于给定点权的点跑最短路” 即为简单,在跑最短路的时候加一个判断,下一个相邻点点权大于给定点权则直接跳过这个相邻点。别忘了起点的点权也要算在里面!
不存在相关路径更简单,将给定点权设置为足够大跑最短路,判断这时是否成立即可
剩下的就是最短路的事情了,详见代码
链式前向星存无向图不开双倍空间,RE 的就是你!!!
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
二分 + dij 最短路
*/
const int maxm = 1e4 + 5, mod = 998244353, maxn = 5e4 + 5;
const ll inf = 1e14;
int n, m, b, cnt = 1, head[maxm];
vector<int> f(maxm);
struct edge{
int to, w, next;
}p[maxn << 1];
void add_edge(int u, int v, int w){
p[cnt].to = v;
p[cnt].next = head[u];
p[cnt].w = w;
head[u] = cnt ++;
return ;
}
bool check(int ff){
priority_queue<pll, vector<pll>, greater<pll>> q;
vector<bool> vis(n + 1, false);
vector<ll> dis(n + 1, inf);
q.push({0, 1});
pll t;
while(!q.empty()){
t = q.top(); q.pop();
if(vis[t.second]) continue;
vis[t.second] = true;
dis[t.second] = t.first;
for(int i = head[t.second]; i; i = p[i].next){
int v = p[i].to;
if(f[v] > ff) continue;
if(!vis[v] && dis[v] > dis[t.second] + p[i].w){
q.push({dis[t.second] + p[i].w, v});
}
}
}
if(dis[n] > b) return false;
else return true;
}
void solve(){
cin >> n >> m >> b;
for(int i = 1; i <= n; ++ i){
cin >> f[i];
}
for(int i = 0; i < m; ++ i){
int x, y, c;
cin >> x >> y >> c;
add_edge(x, y, c);
add_edge(y, x, c);
}
if(!check(mod)){ //所有城市均可选择时也无法抵达
cout << "AFK\n"; return ;
}
int l = f[1], r = 1e9 + 1, mid; //二分起点应该是第一座城市的费用,终点应当大于最大城市的费用
while(l <= r){ //二分答案
mid = l + r >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
cout << l << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
// cin >> _;
while(_ --){
solve();
}
return 0;
}
最小值最大化
- 洛谷 P1824 进击的奶牛
简单题,二分答案距离 + check 函数即可
代码:Qiansui_code
实数二分
题意
有 f 个朋友来参加派对,而我有 n 个口味大小不一的派。每位朋友都会分到一个同样大小的派,且要求每个人的派可以是一整个派,但不能是几块拼成的派。我也要给自己留一块一样的。
每个派都是高为 1,半径不等的派。
问最后每个人能分到的最大的派是多少?
思路
实数二分答案
代码
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x, y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x, y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
别忘了主人也是要分一块蛋糕的
*/
const int maxm = 1e4 + 5, inf = 0x3f3f3f3f, mod = 998244353;
const double pi = acos(-1.0), eps = 1e-5;
int n, f;
double r[maxm];
bool check(double mid){
int sum = 0;
for(int i = 1; i <= n; ++ i){
sum += r[i] / mid;
}
if(sum >= f) return true;
else return false;
}
void solve(){
cin >> n >> f;
++ f;
double x = 0, y = 0, mid;
for(int i = 1; i <= n; ++ i){
cin >> r[i];
r[i] = r[i] * r[i] * pi;
y = max(y, r[i]);
}
while(y - x > eps){
mid = (x + y) / 2.0;
if(check(mid)) x = mid;
else y = mid;
}
cout << fixed << setprecision(4) << y << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while(_ --){
solve();
}
return 0;
}
例题
简单题 abc 312 C - Invisible Hand
利用二分在范围 [1, 1e9] 内找到我们想要的满足题意的 X,而 l 和 r 转向的条件就是人数是否满足,满足则r--,找最小的 X,最终答案即为 l
代码:qiansui_code
cf 890 div2 C. To Become Max
题意
给你一个长度为 n 的序列 a,你可以执行操作:选择一个下标 i 且 i 满足 \(1 \le i \le n - 1 \; and \; a_i \le a_{i + 1}\),使得\(a[i] ++\)
问你通过最多通过 k 次操作可以获得的数组元素的最大值?
思路
枚举每一个位置上最多能执行几次操作后获得的元素最大值,利用二分枚举判断
注意到题目的构造一定是从后往前搭楼梯一般一层一层建起的,故可以利用这一特点用于判断
详见代码
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
*/
const int maxm = 1e3 + 5, inf = 0x3f3f3f3f, mod = 998244353;
ll n, k, a[maxm], ans;
ll check(int p, int h){
ll sum = 0;
for(int i = p, x = h; i <= n && x > a[i]; ++ i, -- x){ //判断需要的最少的花费
if(i == n) return k + 1; //最后一位无法操作
sum += x - a[i];
}
return sum;
}
void solve(){
cin >> n >> k;
ans = 0;
for(int i = 1; i <= n; ++ i){
cin >> a[i];
ans = max(ans, a[i]);
}
for(int i = 1; i <= n; ++ i){
ll l = a[i], r = a[i] + k, mid;
while(l <= r){
mid = l + r >> 1;
if(check(i, mid) <= k) l = mid + 1;
else r = mid - 1;
}
ans = max(ans, r);
}
cout << ans << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
cin >> _;
while(_ --){
solve();
}
return 0;
}
洛谷 P1419 寻找段落
题意
给定一个长度为 \(n\) 的序列 \(a\),定义 \(a_i\) 为第 \(i\) 个元素的价值。现在需要找出序列中最有价值的“段落”。段落的定义是长度在 \([S, T]\) 之间的连续序列。最有价值段落是指平均值最大的段落。段落的平均值 等于 段落总价值 除以 段落长度。
思路
朴素的枚举区间显然TLE
我们重新回顾一下题面,题目要求我们找这么一个东西:$ans_{max} = \frac{\displaystyle \sum_{i = l}^{r} a_i}{r - l + 1} $ 且 $s \le r - l + 1 \le t $
将其进行转化,可以得到 \(\displaystyle \sum_{i = l}^{r} a_i = ans * (r - l + 1)\)
我们可以知道的是,答案 ans 所求的是整个序列里面的最大值,其显然满足单调性,且 ans 的范围即为 \([a_{i\ min}, a_{i\ max}]\),所以我们可以将问题转化为二分答案,判断其是否满足 \(\displaystyle \sum_{i = l}^{r} a_i \ge ans * (r - l + 1)\),即 \(\displaystyle \sum_{i = l}^{r} (a_i - x) \ge 0\)
为了加快我们的判断,我们可以对原式求前缀和,则上一式子转化为 $sum_r - sum_{l - 1} \ge 0 $
可以利用递增的单调队列优化判断
详见代码
代码
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
*/
const int maxm = 1e5 + 5, inf = 0x3f3f3f3f, mod = 998244353;
const double eps = 1e-5;
int n, s, t, a[maxm];
bool check(double mid){
vector<double> b(n + 1);
for(int i = 1; i <= n; ++ i){
b[i] = a[i] - mid + b[i - 1];
}
deque<int> q;//维护一个单增的单调队列
for(int i = s, p = 0; i <= n; ++ i, ++ p){
while(!q.empty() && b[q.back()] > b[p]) q.pop_back(); //去尾
q.push_back(p);
while(q.front() < i - t) q.pop_front(); //删头
if(b[i] - b[q.front()] >= 0) return true; //该区间差值最大满足条件
}
return false;
}
void solve(){
cin >> n >> s >> t;
for(int i = 1; i <= n; ++ i){
cin >> a[i];
}
double l = -1e4, r = 1e4, mid;
while(r - l > eps){
mid = (l + r) / 2.0;
if(check(mid)) l = mid;
else r = mid;
}
cout << fixed << setprecision(3) << l << '\n'; //要求输出3位!
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
// cin >> _;
while(_ --){
solve();
}
return 0;
}
洛谷 P1083 [NOIP2012 提高组] 借教室
题意不再复述了,本题利用二分 + 差分前缀和维护暴力可以通过
二分枚举订单号 x,差分 + 前缀和维护前 x 个订单需要的教室数,再判断每天的教室数是否满足上限即可
Qiansui_code
洛谷 P2678 [NOIP2015 提高组] 跳石头
二分跳跃距离 mid,判断时跳跃距离小于 mid 的石子均移除
注意,在判断的时候需要考虑终点这一位置的影响
特殊情况见链接
Qiansui_code
ABC 319 D - Minimum Width
简单的二分答案
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17591660.html