CF1678 B2 Tokitsukaze and Good 01-String (hard version)

文章讨论了一道编程题目,如何通过最少的操作次数,将给定的二进制字符串转换为可以偶数划分的字符串,并找出划分成最少段数的方法。作者提出了一种贪心策略,利用数组表示每个字符连续子串的长度,通过调整奇偶性来达到目标。
摘要由CSDN通过智能技术生成

我讨厌思维题 /(ㄒoㄒ)/~~

 原题链接:Problem - 1678B2 - Codeforces

题意翻译

每次给定一个长度为 n 的 01 串 s 。你可以对它做出 0 次或任意次操作。每次操作为选择 1 位并将其修改为 0 或 1,使得 s 是好的。

一个字符串是好的,如果他存在一种字符串划分方式,分成 长度为偶数的不交字串且一个字串中所有字符均相同 。如 s="11001111",它可以被分成 "11", "00" 和 "1111",他们的长度分别为 2,2,4(都是偶数),他是好的。另一个例子 s="1110011000",它可以被分成  "111", "00", "11" 和 "000",他们的长度分别为 3,2,2,3,不是好的。

你还需要输出将字符串变成 好的 的最小操作数,并输出在这种操作数下,使得你所划分出的字串数量最小,并输出最小字串数目。

输入格式

第一行包含一个正整数 t(1≤ t ≤ 10000)——表示测试用例的数量。

对于每个测试用例,第一行包含单个整数 n(2 \leq n \leq 2*10^{5})——表示 s 的长度,保证n是偶数。

第二行包含长度为 n 的二进制字符串 s,该字符串仅由0和1组成。

第二行包含 n 个整数 a_{i} ——表示点的点权。

保证所有测试用例的 n 之和不超过2*10^{5}

输出样例

思路

错误:

将相同的字符合并,可以得到一个数组 a[] ,a[i] 表示第 i 个串的长度,如 s="1110011000" 可以变成 a[]={0, 3, 2, 2, 3} ( a[i] 中根据 i 可以求出这个串里面放的是 0 还是 1,即 i%2=0/1,会方便一点)。

想让字符串变成 好的,即 a[] 全为偶数,那么一个奇数一定要和另一个奇数凑起来,而一次操作,会让相邻的两个 a[i]、a[i+1] 变成 a[i]+1、a[i+1]-1,即一个加,一个减。那么最小的操作数就是 两个奇数之间数量+1 。

没写出来的部分 (;′⌒`) :要求字符串最少分成几段,如果 a[i] 是偶数,ans++;如果 a[i] 是奇数,那么我们记录 a[i] 到下一个奇数 a[j] 之间所有 a 的奇偶数量,如 s="1110011000" 时,i=1; ji=2; ou=2。如果 ji+(i%2)+(j%2)==num+1 那么中间的数字都可以变成奇数,ans自然比按位+1 -1 小,同理 如果 ou+2-(i%2)-(j%2)==num+1 那么中间的数字都可以变成偶数,然后就写不出来了

样例有:

2
4
1000
4
0100

结论:我讨厌思维题 ┗|`O′|┛!!

正确:

贪心的考虑,每个段都是偶数,那我们完全可以分成长度都为 2 的小段,那么最小操作数就是所有 不同小段(小段内两个数不相同)的数量 num。

至于最少段数,
如果 小段内两个数字相同,不对 num 做出贡献,也就不对 ans 做出贡献;
如果 小段内两个数字不同,那么我们要么让他和左边的小段一样,要么和右边的小段一样 比较优(因为会少一个段),而右边的小段时一样的还是不一样的并不一定,但是左边的小段一定时一样的(因为我们从左边遍历过来的,不一样的也已经操作一样了),所以我们选择和左边的小段一样。
特殊情况:第一个小段就不一样,那他没有左边的小段,我们一直向右找,找到第一个相同的小段,就变成这个。

代码

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

const int N = 1e6 + 7;
const int mod = 1e9+7;

int n, ans, ans1;
char s[N];

void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>s[i];
	
	s[0]=s[1]; ans=0; ans1=1;
	if(s[1]!=s[2]){//如果第一个小段就不一样,向右找到第一个相同的小段
		for(int i=3;i<=n;i=i+2){
			if(s[i]==s[i+1]){
				s[0]=s[i]; break;
			}
		}
	}
	for(int i=1;i<=n;i=i+2){
		if(s[i]!=s[i+1]){
			s[i]=s[i-1]; s[i+1]=s[i-1]; ans++;
		}
		if(s[i]!=s[i-1]){
			ans1++;
		}
	}
	cout<<ans<<" "<<ans1<<"\n";
}

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int Case=1;
	cin>>Case;
	while(Case--) solve();
	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值