Codeforces Round #712 (Div. 2) B. Flip the Bits 题解

旅行传送门:快来点我鸭

B. Flip the Bits

time limit per test 1 second
memory limit per test 256 megabytes
input standard input
output standard output
There is a binary string a of length n. In one operation, you can select any prefix of a with an equal number of 0 and 1 symbols. Then all symbols in the prefix are inverted: each 0 becomes 1 and each 1 becomes 0.

For example, suppose a=0111010000.

In the first operation, we can select the prefix of length 8 since it has four 0’s and four 1’s: [01110100]00→[10001011]00.
In the second operation, we can select the prefix of length 2 since it has one 0 and one 1: [10]00101100→[01]00101100.
It is illegal to select the prefix of length 4 for the third operation, because it has three 0’s and one 1.
Can you transform the string a into the string b using some finite number of operations (possibly, none)?

Input
The first line contains a single integer t (1 ≤ t ≤ 10 ^ 4) — the number of test cases.

The first line of each test case contains a single integer n (1 ≤ n ≤ 3⋅10 ^ 5) — the length of the strings a and b.

The following two lines contain strings a and b of length n, consisting of symbols 0 and 1.

The sum of n across all test cases does not exceed 3⋅10 ^ 5.

Output
For each test case, output “YES” if it is possible to transform a into b, or “NO” if it is impossible. You can print each letter in any case (upper or lower).

Example
inputCopy
5
10
0111010000
0100101100
4
0000
0000
3
001
000
12
010101010101
100110011010
6
000111
110100
outputCopy
YES
YES
NO
YES
NO
Note
The first test case is shown in the statement.

In the second test case, we transform a into b by using zero operations.

In the third test case, there is no legal operation, so it is impossible to transform a into b.

In the fourth test case, here is one such transformation:

Select the length 2 prefix to get 100101010101.
Select the length 12 prefix to get 011010101010.
Select the length 8 prefix to get 100101011010.
Select the length 4 prefix to get 011001011010.
Select the length 6 prefix to get 100110011010.
In the fifth test case, the only legal operation is to transform a into 111000. From there, the only legal operation is to return to the string we started with, so we cannot transform a into b.

题目大意
给你一个长度为n的二进制字符串a。 每次操作可以选择带有0和1字符数相等的a的任何前缀。 然后前缀中的所有符号都将反转:每个0变为1,每个1变为0。
问你在经过若干次操作后,字符串a是否可以转换为字符串b。

解题思路
首先此题有几个要注意的地方:

1.每次操作变换的是字符串a中长度为i的前缀(即是对字符串a的前i个字符取异或)

2.每次操作选取的前缀中0和1的字符数相等

试想一下,如果是模拟,我们应该怎么做?

首先从末尾开始对两字符串进行逐个比较,如果ab第i个数字互异,则将a中长度为i且01字符数相等的前缀全部取异或,然后从当前位置继续比对,直至无法修改或a转换成b为止。显然,每次都修改并重新统计的话是行不通的,复杂繁琐的代码与极高的时间复杂度令人望而却步。

那么我们先统计字符串ab中数字1的个数,然后反向处理字符串,用一个指针记录后缀相同的位置,因为每次修改后前缀中匹配与不匹配的关系就互换了,所以若是进行过奇数次修改,那就从修改处开始往前搜索两字符串最初匹配的部分直至遇到不匹配的;否则就搜索不匹配的部分。如果某次操作时选取的前缀中0和1的字符数不等或ab两字符串的前缀中0或1的数目不相等,说明a不可能转换为b,如果能一直修改到底,说明可以成功转换。

笔者语文水平有限,可能文字说明比较晦涩难懂,以样例来作进一步阐释说明:

string a = 010101010101
string b = 100110011010

先统计a、b中1的个数均为6

从末尾向前搜索,长度为4的后缀不同,记录i的位置为8,第一次操作:[010101010101] → [101010101010]

从i = 8的位置继续向前搜索,此时原字符串中以i为长度的前缀与b的匹配关系相反,读出原来7-8的字符匹配(奇数次操作后不匹配了),第二次操作:[10101010]1010→ [01010101]1010

从i = 6的位置继续向前搜索,异或偶数次后原字符串中以i为长度的前缀又变回最初的样子,读出5-6的字符不匹配,第三次操作:[010101]011010→ [101010]011010

从i = 4的位置继续向前搜索,奇数次,找原来匹配的3-4,第四次操作:[1010]10011010→ [0101]10011010

最终操作:[01]1010011010→ [10]0110011010

不难发现,上述操作选取的前缀中0和1的字符数都保持相等且ab两字符串的前缀中0或1的数目也相等

再举两个反例吧:

string a = 001
string b = 000

一开始统计后就能发现ab串中0、1数目不等,直接pass

string a = 000111
string b = 110100

第一次操作后:a → 111000

从i = 4处往前搜索,奇数次找原来的匹配项,3-4相等,但此时两条件都不满足,说拜拜~

剩下的看代码呗,如果有疑惑或者更好的作法欢迎在评论区与小蒟交流,文笔不足之处还请见谅(✿◡‿◡)

AC代码

#include <bits/stdc++.h>
#define MAXN 300000 + 10

char s1[MAXN], s2[MAXN];

int main(int argc, char const *argv[])
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        /**
         * sum1 a中字符1的数目
         * sum2 b中字符1的数目
         * cnt 操作次数
         */
        int n, sum1 = 0, sum2 = 0, flag = 1, cnt = 0;
        scanf("%d", &n);
        scanf("%s%s", s1 + 1, s2 + 1); //字符串偏移
        for (int i = 1; i <= n; i++)   //统计字符串ab中1的数目
        {
            sum1 += s1[i] - '0';
            sum2 += s2[i] - '0';
        }
        if (sum1 != sum2) //开局不相等就可以直接/remake了
        {
            puts("NO");
            flag = 0;
            continue;
        }
        for (int i = n; i; i--) //i作为字符串指针
        {
            if (s1[i] == s2[i])
            {
                if (!(cnt % 2)) //偶数次异或操作还原,找不匹配的部分
                {
                    sum1 -= (s1[i] - '0');
                    sum2 -= (s2[i] - '0');
                    continue;
                }
                else //奇数次操作匹配关系互换,找最初匹配的部分
                {
                    while (s1[i] == s2[i] && i)
                    {
                        sum1 -= (s1[i] - '0');
                        sum2 -= (s2[i] - '0');
                        i--;
                    }
                    if (sum1 != sum2 || i - sum1 != sum1) //两个条件不满足其一都不能完成转换
                    {
                        puts("NO");
                        flag = 0;
                        break;
                    }
                    i++, cnt++; //之前的while循环指针往前多挪了一位
                }
            }
            else
            {
                while (s1[i] != s2[i] && i)
                {
                    sum1 -= (s1[i] - '0');
                    sum2 -= (s2[i] - '0');
                    i--;
                }
                if (sum1 != sum2 || i - sum1 != sum1)
                {
                    puts("NO");
                    flag = 0;
                    break;
                }
                i++, cnt++;
            }
        }
        if (flag)
            puts("YES");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值