二分是ACM中十分常用的技巧,在有序,或者更准确说是单调递增(减)条件下,可以快速查找一个需要的数字。常用于:在时间最小的情况下(二分时间),在最长边最短的情况下(二分长度 )。
但是我这么久了依然写不好二分。本菜鸡决定写个总结。
二分的基本模板:
while(front <= back) {
int mid = (front + back) >> 1;
if(Check(mid)) {
front = mid + 1;
} else {
back = mid - 1;
}
}
这个模板并不是固定的。主要看题目要求和实现的功能。
必须要保证front或者back在动。有的二分写法可以写成front=mid+1,back=mid,这种有局限性而且容易乱,我已经抛弃这种写法了。
1.我们先看一个例子
如果我们想要在一个有序的数组num里面找到大于等于x的第一个数字,那么上面的Check就可以写成
if(num[mid] < x)
return 1;
return 0;
2.此时如何判断我们要的是front还是back位置的数字呢?
我们可以发现,如果满足要求,back将会向着不满足要求的方向跑,如果不满足要求,front则向着满足要求的方向跑,所以答案可想而知。
我们只需要取出num[front]就是我们想要的答案。
3.边界问题处理起来也容易乱,所以边界又应该怎么写呢?
我们可以思考一些小数据,然后推广到大数据中。比如num数组是[1,5,7,90],如果我们令左边界front=0,右边界back=3,是否足够呢?
如果x=0,自己模拟一下,会有front=0,back=-1,此时num[front]=1就是答案。
如果x=99,会有front=4,back=3,此时num[front]已经不在给定的数组范围内了。究其原因,是因为我们的条件,大于等于x的第一个数,很明显数组里面没有这种数字,这时候
二分跑的过程是对的,不过结果不存在。这时候看题目要求了,该特判就特判什么的。
所以我们只需要把数组的左右边界代入二分即可。
上面的例子十分简单,但是竞赛中常用的二分并不会让你单纯在数组中查找值,那太简单了。我们看一个CF上的简单题,975C。
链接:
975C
题目大意:给出n和m,然后有n个人进攻,战斗力为ai,m次防守的火力,为ki。
防守火力只会从没倒下的第一个人开始攻击。
如果一个人战力为3,受到1点防守火力,则剩下2战力。
如果攻击的人战斗力为1 1 1,受到5点防守火力,则全部人阵亡,然后重新复活,此时认为全部人存活。
要求输出存活的人的人数。
解法:直接将攻击方的战斗力做一个前缀和sum,然后读入防守火力,不断加起来得到defend。
此时
其实就是找到第一个大于defend的sum,二分查找即可。
如果某次发现防守火力大于进攻火力之和,即front=n+1,像上面x=99那样超出范围了。那么我们进行特判,此时输出n,清除防守火力,令defend=0即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e5 + 5;
int n, q;
LL sum[maxn], a[maxn], b[maxn], attack_tmp = 0;
int main() {
#ifndef ONLINE_JUDGE
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin >> n >> q;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
for(int i = 1; i <= n; i++) {
sum[i] = a[i] + sum[i - 1];
}
for(int i = 0; i < q; i++) {
LL attack;
cin >> attack;
attack_tmp += attack;
int front = 1, back = n;//按照上面说的,边界直接设为1和n即可
while(front <= back) {
int mid = (front + back) >> 1;
if(sum[mid] <= attack_tmp) {
front = mid + 1;
} else {
back = mid - 1;
}
}
if(front <= n)//按照上面说的,front是我们要的答案
cout << n - front + 1 << endl;
else {
attack_tmp = 0;
cout << n << endl;
}
}
return 0;
}
这个题目是不是很简单,二分这种东西,万变不离其宗。即使是区域赛需要用到二分的题目也是适用上面的讨论的。尝试了一下以前做的题目,按照上面的思路,条理清晰多了,完美。
最后掏出一道区域赛难度需要二分的题目来吓人。http://codeforces.com/contest/852/problem/D
由于题目要求最短时间,所以我们想到了二分时间(都是套路)。先floyd预处理出每个点之间的时间,然后二分时间,只把边权小于mid的边加入网络流的图中,不断跑网络流即可。
当时我用的二分是一个动,一个不动的,使得我脑子乱的不行,现在总结后再也不用那种二分了。
当时的代码:https://blog.csdn.net/llzhh/article/details/77833515
只改二分部分:(从120行开始)
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<utility>
#include<stack>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<map>
using namespace std;
const int maxn = 10005;
const int maxm = 1000005;
const int INF = 0x3f3f3f3f;
int head[maxn],cur[maxn],nx[maxm<<1],to[maxm<<1],flow[maxm<<1],ppp=0;
struct Dinic {
int dis[maxn];
int s, t;
int ans;
void init() {
memset(head, -1, sizeof(head));
ppp = 0;
}
void AddEdge(int u, int v, int c) {
to[ppp]=v;flow[ppp]=c;nx[ppp]=head[u];head[u]=ppp++;swap(u,v);
to[ppp]=v;flow[ppp]=0;nx[ppp]=head[u];head[u]=ppp++;
}
bool BFS() {
memset(dis, -1, sizeof(dis));
dis[t] = 1;
queue<int> Q;
Q.push(t);
while(!Q.empty()) {
int x = Q.front();
Q.pop();
for(int i = head[x]; ~i; i = nx[i]) {
if(flow[i^1] && dis[to[i]] == -1) {
dis[to[i]] = dis[x] + 1;
Q.push(to[i]);
}
}
}
return dis[s] != -1;
}
int DFS(int x, int maxflow) {
if(x == t || !maxflow){
ans += maxflow;
return maxflow;
}
int ret = 0, f;
for(int &i = cur[x]; ~i; i = nx[i]) {
if(dis[to[i]] == dis[x] - 1 && (f = DFS(to[i], min(maxflow, flow[i])))) {
ret += f;
flow[i] -= f;
flow[i^1] += f;
maxflow -= f;
if(!maxflow)
break;
}
}
return ret;
}
int solve(int source, int tank) {
s = source;
t = tank;
ans = 0;
while(BFS()) {
memcpy(cur, head, sizeof(cur));
DFS(s, INF);
}
return ans;
}
}dinic;
typedef pair<int, int> pii;
vector <pii> edge[605];
int cnt[605], dis[605][605];
int n, m, e;
void floyd() {
for(int k = 1; k <= m; k++) {
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m; j++) {
dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);
}
}
}
}
int main() {
int k;
ios::sync_with_stdio(0);
cin >> m >> e >> n >> k;
for(int i = 1, tmp; i <= n; i++) {
cin >> tmp;
cnt[tmp]++;
}
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m; j++) {
dis[i][j] = INF;
}
}
for(int i = 0, u, v, val; i < e; i++) {
cin >> u >> v >> val;
if(u == v)
continue;
if(dis[u][v] > val)
dis[u][v] = dis[v][u] = val;
}
floyd();
int front = 0, back = 1731311;
while(front <= back) {
int mid = (front + back) / 2;
dinic.init();
int s = 0, e = 2 * m + 1;
for(int i = 1; i <= m; i++) {
dinic.AddEdge(s, i, cnt[i]);
dinic.AddEdge(m + i, e, 1);
}
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m; j++) {
if(dis[i][j] <= mid || i == j)
dinic.AddEdge(i, m + j, INF);
}
}
int Max_Flow = dinic.solve(s, e);
if(Max_Flow >= k) {
back = mid - 1;
} else {
front = mid + 1;
}
}
if(front <= 1731311)
cout << front << endl;
else
cout << -1 << endl;
return 0;
}