CCPC桂林 - G. Occupy the Cities —— 二分答案

10 篇文章 4 订阅
题目来源
题意:

给定一个长度为 n 的 01 数列。对于每轮操作,如果一个位置上为 1,那么其可以将相邻的最多一个位置上的 0 变成 1。
问,将所有 0 都变成 1 至少需要多少轮操作?

分析:

看这道题之前先看一道简单题:

给定一个长度为 n 的 01 数列,对于每轮操作,如果一个位置上为 1,那么其可以将相邻的最多两个位置上的 0 变成 1。
问,将所有0 都变成 1 至少需要多少轮操作?

对于这道题来说,每个 1 可以向左右两个位置扩展,可以用贪心,也可以二分答案。
二分的话,每次 check 判断左右最近的 1 的距离是否不超过mid。

然后回到这题:
这道题与上面那题的区别就是,每个 1 需要向左或者向右延伸一个位置之后,才能向左右两个位置扩展。
所以,对于每个 0 来说,其变成 1 的最小操作轮数为,其左右最近的 1 的距离 dis,或者 dis + 1(如果那个 1 先往另一方向扩展的话)。

二分最少操作次数 mid,check:

对于每个 0 来说:

  • 如果距离其最近的1的距离 + 1 <= mid 的话,那么无论那个 1 先往哪扩展,这个 0 一定能在 mid 次操作后被扩展。
  • 如果距离其最近的1的距离 = mid 的话,那么需要这个1先往其方向扩展才能保证第 mid 次操作将其扩展。
    因为是从前往后走,所以如果左右两边的1距离相同的话,优先考虑标记左边的1,把右边的1留给后面。每个1只能先向一个方向扩展,标记方向只能一次。
bool check(int mid)
{
	for(int i=1;i<=n;i++) f[i] = 0; //初始未标记
	
	for(int i=1;i<=n;i++)
	{
		if(a[i] == '1') continue;
		
		int mina = min(i-pre[i], last[i]-i) + 1; //左右两边最近1的距离 + 1
		if(mina <= mid) continue; //不管方向都能在mid次操作内扩展
		
		if(pre[i] >= 1 && !f[pre[i]] && i-pre[i] <= mid){f[pre[i]] = 1; continue;} //优先标记左边的1
		else if(last[i] <= n && !f[last[i]] && last[i]-i <= mid){f[last[i]] = 1; continue;}
		else return 0;
	}
	return 1; //所有的0都能在mid次操作内被扩展
}
完整Code:
#include<bits/stdc++.h>
using namespace std;

const int N = 2000010, mod = 1e9+7;
int T, n, m;
char a[N];
int pre[N], last[N], f[N];

bool check(int mid)
{
	for(int i=1;i<=n;i++) f[i] = 0;
	
	for(int i=1;i<=n;i++)
	{
		if(a[i] == '1') continue;
		
		int mina = min(i-pre[i], last[i]-i) + 1;
		if(mina <= mid) continue;
		
		if(pre[i] >= 1 && !f[pre[i]] && i-pre[i] <= mid){f[pre[i]] = 1; continue;}
		else if(last[i] <= n && !f[last[i]] && last[i]-i <= mid){f[last[i]] = 1; continue;}
		else return 0;
	}
	return 1;
}

signed main(){
	cin >> T;
	while(T--)
	{
		cin>>n;
		cin >> a+1;
		
		int x = -1e9;
		for(int i=1;i<=n;i++)
		{
			pre[i] = x;
			if(a[i] == '1') x = i;
		}
		
		x = 1e9;
		for(int i=n;i>=1;i--)
		{
			last[i] = x;
			if(a[i] == '1') x = i;
		}
		
		int l = 0, r = n;
		while(l < r) 
		{ 
			int mid = l+r>>1;
			if(check(mid)) r = mid;
			else l = mid+1;
		}
		cout << l << endl;
	}
	
	return 0;
}

经验:

整理完之后,发现之前应该做过这样的题目,尽量使用左边的,把右边的留给别人。但是在场上并没有想到二分答案,如果想到了,能不能做出来呢?
同时,碰见这样的题为什么想不到二分答案呢?

这是需要注意的问题。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值