浅谈自己对二分的理解和运用

二分是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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值