目录
题目:
题目理解:
给你长度 n ,再分别给你长度为 n 的 a 字符串和 b 字符串,这两串都是 01字符串。
一次操作可以进行如下操作:
①找出 1 到 n 的一段连续区间(最小长度为 1 )
②将 a 串下标位于区间内(闭区间)的元素进行反转( 1 变 0 , 0 变 1 )
③将 b 串下标位于区间外的元素进行反转( 1 变 0 , 0 变 1 )
目标:找出一种操作次数小于( n + 5 )的操作方法,使得两个字符串里面的元素最终都变成 0
初始思路:
先进行尝试,发现有几个特性:
①区间选为 1 到 n 的话,可以将 a 串完全翻转而同时 b 保持不变
②通过题目给的样例和自己尝试发现似乎只有 a 串和 b 串完全相同过着完全相反才能成功
③ a 串在进行操作的时候,区间外的元素必定不变
④ a 串中的元素是否反转,和此元素被选中在区间内的次数有关(奇数次翻转,偶数次不变)
⑤ b 串在进行操作的时候,区间内的元素必定不变
⑥ b 串中的元素是否反转,和此元素被选中在区间外的次数有关(奇数次翻转,偶数次不变)
⑦如果 a 串 b 串全是 1 或者全是 0 的话会比较特殊
所以初始大致思路如下:
一般情况下如果 a 串 b 串完全相反,先花一次操作(1,n)把 a 串取反,这样 a 串 b 串就一样了。然后把 a 串 b 串的所有数都变成 1 ,然后花 3 次操作全变成 0 ,即(1,1)(2,n)(1,n)
那么我们的问题就转变成了,如何将 ab 串都转变成全 1 的。详情可以观看下图
因为前面的操作确保了 a 串 b 串是完全相同的,我们可以先找到一段连续的 1 ,然后经过红色区间和蓝色区间两次操作之后,我们会发现,我们只会对绿色区间里的元素进行转换,而绿色外面的元素经过两次操作会保持不变。这样我们就实现了将绿色区间里面的所有元素都转变成了 1 。在此基础上,我们不断找后面的连续的 1 ,就可以完成对整个 ab 串都转换成 1 的操作。
然后就像上面说的那样,花3次操作全变成 0 ,即(1,1)(2,n)(1,n)就可以将 ab 串都变成全 0 的。
emmm大致思路是这样的,具体操作请看初始思路AC代码。
可能代码略微复杂,但我认为这也是一种很好的思路,所以我还是想保留下来,如果看不懂这种思路的代码请移步下面更优化的思路及其代码
初始思路AC代码:
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
char a[N];
char b[N];
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
cin >> a + 1;
cin >> b + 1;
int flag = a[1] == b[1];//a串b串第一个位置相同还是相反的状态
int ooo = 0;//ab串是否完全相同或完全相反的标志
for (int i = 1; i <= n; i++)
{
if ((a[i] == b[i]) != flag)
{
ooo = 1;
cout << "NO" << '\n';
break;
}
}
if (ooo)continue;
cout << "YES" << '\n';
vector<pair<int, int>> ans;
if (flag == false)
ans.push_back({ 1,n });
int f = 1;//(a串全为1,b全为0)(a全为0,b全为0)的时候f == 1
for (int i = 1; i <= n; i++)
if ((a[i] == '0') != flag)
{
f = 0;
break;
}
if (f == 1)
{
cout << ans.size() << '\n';
if (flag == false)
cout << 1 << " " << n << '\n';
continue;
}
int l, r;
for (l = 1; l <= n; l++)if ((a[l] == '1') == flag)break;
for (r = l; r <= n; r++)if ((a[r] == '0') == flag)break;
ans.push_back({ 1,r - 1 });
for (; r <= n; r++)if ((a[r] == '1') == flag)break;
ans.push_back({ l,r - 1 });
while (r <= n)
{
l = min(r, n);
for (; r <= n; r++)if ((a[r] == '0') == flag)break;
ans.push_back({ l,r - 1 });
for (; r <= n; r++)if ((a[r] == '1') == flag)break;
ans.push_back({ l,r - 1 });
}
ans.push_back({ 1,1 });
ans.push_back({ min(2,n),n });
cout << ans.size() << '\n';
for (int i = 0; i < ans.size(); i++)
cout << ans[i].first << " " << ans[i].second << '\n';
}
return 0;
}
更优化的思路:
所以我们总结以下,什么会影响 b 串?其一是对 a 的操作次数,其二是 a 串与 b 串是相同还是相反
所以我们可以简化以下思路,不用特意对 a 串中连续的 1 进行操作,直接对 a 串中每一个 1 进行单独操作也可以,会有大致以下四种情况:
① a 串中 1 的个数为奇数, a 串 b 串完全相同
② a 串中 1 的个数为奇数, a 串 b 串完全相同
③ a 串中 1 的个数为偶数, a 串 b 串完全相反
④ a 串中 1 的个数为奇数, a 串 b 串完全相反
对 a 串中所有 1 的位置进行单独操作后, a 串都会变成全 0 的状态,但是 b 串不一定,其中 ② 和 ④ 的 b 串会变成全 0 的状态,此时符合题意。 但 ① 和 ③ 的 b 串会变成全 1 的情况,此时还需要多做三步,即(1,1)(2,n)(1,n)。
那么思路有了,具体操作请看更优化的思路的AC代码
更优化的思路的AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
char a[N];
char b[N];
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t; cin >> t;
while (t--)
{
int cnt = 0;//操作次数
vector<pair<int, int>>v;//操作步骤
int n;
cin >> n;
cin >> a + 1;
cin >> b + 1;
int flag1 = 1;//ab串都相同
int flag2 = 1;//ab串都相反
for (int i = 1; i <= n; i++)
{
if (a[i] != b[i])flag1 = 0;
if (a[i] == b[i])flag2 = 0;
}
if (flag1 || flag2)
cout << "YES" << '\n';
else
{
cout << "NO" << '\n';
continue;
}
for (int i = 1; i <= n; i++)
{
if (a[i] == '1')
{
v.push_back({ i,i });
cnt++;
}
}
if ((flag1 && cnt % 2 == 1) || (flag2 && cnt % 2 == 0))
{
v.push_back({ 1,n });
v.push_back({ 1,1 });
v.push_back({ 2,n });
cnt += 3;
}
cout << cnt << "\n";
for (auto& [x, y] : v)
cout << x << " " << y << '\n';
}
return 0;
}