二分算法专题

Codeforces Round 881 (Div. 3)

E

题目链接

Problem - E - Codeforces

题目大意

给定n个全为0的序列,m个区间l,r (l<=r),定义一个区间是正确的,有且仅当这个区间内的1的数量严格大于0的数量。给定q次修改,每次修改给出一个i,表示将索引i修改为1,求最少需要多少次修改,使得至少有一个区间是正确的。

数据范围

1≤m≤n≤1e5,1≤q≤n

思路

看到这种求答案最值的问题,可以往二分答案这个角度来想,可以发现这个答案具有单调性,所以最重要的就是如何写check()函数。我们发现,每次修改都是从上到下的修改,我们二分答案,即二分修改次数时,可以根据上一次的修改次数进行增或减,即对把少修改的0改为1,多修改的1改为0。查询区间是否是正确的,可以遍历一遍区间数组,对区间求和,判断是否区间的和严格大于区间长度的一半。而这个操作可以通过一个树状数组进行,即单点修改,区间查询。

做法

写一个二分板子,然后写一个check()函数,然后再check()函数中套一个树状数组,进行单点修改和区间查询。

标签

二分、数据结构、树状数组

代码实现

int n, m;
int tree[maxn];
void init() {
	for (int i = 1; i <= n; ++i) tree[i] = 0;
}
void add(int i,int x) {
	while (i <= n) {
		tree[i] += x;
		i += lowbit(i);
	}
}
int query(int i) {
	int res = 0;
	while (i > 0) {
		res += tree[i];
		i -= lowbit(i);
	}
	return res;
}
void solve() {
	cin >> n >> m;
	init();
	vector<pii>a(m + 1);
	for (int i = 1; i <= m; ++i) {
		cin >> a[i].first >> a[i].second;
	}
	int k;
	cin >> k;
	vector<int>q(k + 1);
	for (int i = 1; i <= k; ++i) cin >> q[i];
	int pre = 0;
	auto check = [&](int x) {
		if (pre <= x) {
			for (int i = pre+1; i <= x; ++i) { //把少修改的0改为1
				add(q[i], 1);
			}
		}
		else {
			for (int i = pre; i >= x+1; --i) { //把多修改的1改为0
				add(q[i], -1);
			}
		}
		for (int i = 1; i <= m; ++i) { //查询m个区间是否存在符合的情况
			if ((query(a[i].second) - query(a[i].first - 1)) * 2 > a[i].second - a[i].first + 1) {
				return true;
			}
		}
		return false;
		};
	int l = 1, r = k, res = -1;
	while (l <= r) { //二分答案
		int mid = l + r >> 1;
		if (check(mid)) {
			res = mid;
			pre = mid;
			r = mid - 1;
		}
		else {
			pre = mid;
			l = mid + 1;
		}
	}
	cout << res << endl;
}

时间复杂度

O(m*log2n)

Rating

1600

Codeforces Round 878(Div. 3)

D

题目链接

Codeforces Round 878 (Div. 3)

题目大意

给定n个数的序列a,可以选择任选三个数x,y,z使得MIN(MAX(abs(x-ai),abs(y-ai),abs(z-ai)))最小。

数据范围

1≤n≤2e5,1≤ai≤1e9

思路

看到这种求最值的问题,又仿佛无从下手的题目,不妨试一下二分答案。注意的是,二分答案的情况通常是答案具有单调性,也就是比最终的答案更宽限条件的答案都能实现,比答案更苛刻的条件都无法实现。如果能够判断这一点,那么就可以从这个角度来上手这道题。此外,我们还需要一个check函数来判断答案是否可行。注意的是,check函数一般是要使用贪心的思想,也就是使这个答案最优的情况,如果最优的情况行不通,那么答案一定行不通

做法

这题的答案显然具有单调性,最差的答案显然是max(a1,a2...an)(当然可以更小,为了简单就设为此),因为可以选x=y=z=0,最好的答案是0,找到了边界,那么我们就可以写一个check函数,来判断答案是否可行。这里可以把数组排序一下,然后枚举每一个点,设x为a1+ans(二分的答案),那么继续判断第二个点距离x的距离,距离大于ans的话就加入第二个点y=(ai+ans),以此类推设置第三个点z,如果设置多余三个点,那么答案肯定是不行的。因为这样的设置已经是最优的设置了,无法更优了

标签

二分、排序,贪心

代码实现

void solve() {
	int n;
	cin>>n;
	vector<int>a(n+1);
	int maxx=-inf;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		maxx=MAX(maxx,a[i]); //求右边界
	}
	sort(a.begin()+1,a.end());//排序
	int l=0,r=maxx,res=0;
	auto check=[&](int x){//贪心地check
		int pre=a[1]+x;
		int cnt=1;
		for(int i=2;i<=n;++i){ 
			if(abs(pre-a[i])<=x) continue;	
			else{
				++cnt;
				pre=a[i]+x;
			}
			if(cnt>3) return false;
		}
		return true;
	};
	while(l<=r){ //二分答案
		int mid=l+r>>1;
		if(check(mid)){
			res=mid;
			r=mid-1;
		}
		else{
			l=mid+1;
		}
	}
	cout<<res<<endl;
}

时间复杂度

O(log2(1e9)*n)

Rating

1400

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值