codeforces 题目 Binary String

目录

题目:

题目描述:

思路(双指针):

AC代码(双指针):

思路(推导):

AC代码(推导):


题目:

题目描述:

对于每一个案例,给你一个 01字符串,你可以从前或者从后删除一些字符串。这样操作的成本是max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )。问最小的成本是多少?

思路(双指针):

对于双指针,我们最重要的思路就是找到一种单调性。

为了简化表述,我们先不妨设

c0:保留下来的 0 的个数

c1:删除掉的 1 的个数

左指针:保留区间左边界

右指针:保留区间右边界

目标:找到 max( c0 ,c1)的最小值


如果基于花销进行双指针,挪了左指针有可能花销上升,也可能下降,挪右指针花销也可能上升,也可能下降...此时没有单调性。


但是我们换一种思路,基于  c0  和 c1  的个数进行双指针,如果我们右移右指针(即扩增保留区间)的时候:

如果加入到保留区间的是 0 ,那么  c0  增多(保留下来的 0 的个数增多), c1  不变(删除掉的 1 的个数不变)

如果加入到保留区间的是 1 ,那么  c1  减少(删除掉的 1 的个数减少), c0  不变(保留下来的 0 的个数不变)

我们发现,对于扩增来说,  c0 永远是单调递增的,  c1 永远是单调递减的,在同一个左指针下,  c0  c1 的变化感觉大概如下图所示:

并且我们还可以看到, c0  和  c1  并不会同时增加或者减少,一定是一个变化而另一个不变,那么说明在同一个左指针下,我们右移右指针的过程中,必然不会错过最佳位置  c0  和  c1  相等的时刻,也就是在这个左指针下 max( c0 ,c1)能找到的最小值。

在图中我们也可以看出,在右指针移动到了  c0  和   c1  相等的位置(交点)之后,就没有必要再右移了,如果再右移找到的 max( c0 ,c1)只会比之前的大。

左指针右移的时候,  c0 只会递减,  c1 只会递增,且两者必一个变化而另一个不动。说明只会让  c1 变的更大或  c0 变得更小,从而一定拉大二者差距,所以对于新的左指针右指针无需重置,继续往右走即可找到本轮的最小值。

简单来说,就是在左右指针移动的时候,动态维护  c0  c1 ,以此找到所有可能具有最小值的区间

思路有了,具体操作请看AC代码:

AC代码(双指针):

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 2e5 + 5;
const int inf = 2e9 + 5;

char s[N];

int main()
{
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t;
	cin >> t;

	while (t--)
	{
		int ans = inf;
		int c0 = 0;//保留的0的个数
		int c1 = 0;//删去的1的个数

		cin >> s + 1;
		int n = strlen(s + 1);

		for (int i = 1; i <= n; i++)
			if (s[i] == '1')
				c1++;

		for (int i = 1, j = 0; i <= n; i++)
		{
			while (j + 1 <= n && c1 > c0)
			{
				j++;
				if (s[j] == '0')
					c0++;
				else 
					c1--;
			}
			ans = min(ans, max(c0, c1));

			if (s[i] == '0')
				c0--;
			else
				c1++;
		}
		cout << ans << '\n';
	}
	return 0;
}

思路(推导):

为了简化表述,我们先不妨设

b:保留下来的所有0和1个数

b0:保留下来的 0 的个数

b1:保留下来的 1 的个数

s0:删除部分的 0 的个数

s1:删除部分的 1 的个数

总0:01串中所有的 0 的个数

总1:01串中所有的 1 的个数

目标:找到 max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )的最小值


我们对目标进行一个推导和转化:

原:max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )

 = max(b0, s1)

= max(b0,总1 - b1)

= max(b0,总1 - b1) + b1 - b1

= max(b0 + b1,总1 ) - b1

= max(b,总1)- b1

推导到这里,似乎推不动了,我们进行分类讨论,探查什么时候可能会有最小值:

①当 b >= 总1 时:

min { max(b,总1)- b1 }

= min{ b - b1 }

所以为了让b更接近b1,所以当 b == 总1 时,min{ b - b1 } 有最小值

②当 b <= 总1 时

min { max(b,总1)- b1 }

= min{ 总1 - b1 }

为了让b1更接近总1,所以当 b == 总1 时,min{ 总1 - b1有最小值


综上,两类都有共同的结论,当 b == 总1 时,有最小值,那么我们接着推:

max(b,总1)- b1

= max(总1,总1)- b1

 = 总1 - b1

因为 b == 总1 ,即(保留的长度 == 总1个数 )所以我们现在的目标转换成了,探查所有连续长度为总1的序列中,找到最小的(总1 - b1)

具体实现我们可以用前缀和,就不再详细展开了。

思路有了,具体操作请看AC代码

AC代码(推导):

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 2e5 + 5;
const int inf = 2e9 + 5;

int a[N];//代表制从0~i位置总共有多少个1
char s[N];

int main()
{
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int t; cin >> t;

	while (t--)
	{
		memset(a, 0, sizeof(a));

		cin >> s + 1;
		int n = strlen(s + 1);

		for (int i = 1; i <= n; i++)
		    a[i] = a[i - 1] + (s[i] == '1');

		int m = a[n];//a[n]代表着总1的个数

		for (int i = a[n]; i <= n; i++)
		    m = min(m, a[n] - (a[i] - a[i - a[n]]));
            //上面一行第一个出现的a[n]代表着“总1个数”
            //第二个出现的a[n]代表着“大小为总1个数的长度”
		cout << m << '\n';
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值