题目链接
题目链接(可提交)
题意:
两个长为n(1e5)的二进制串a、b,小地址为低位,现在提供两种操作(任意更改一位,或整串二进制加以),问最少多少步可以使a串变为b串
思路:
策略题首先需要分析两种不同策略的优劣以及应用场景
对于操作一,显然是基本操作,按位变换,最暴力的做法
而相比较而言,操作二就是用来优化的,在某些特殊场景下,操作二可以对串造成较大的变化。
显然,若从最低位开始有连续多个1时,一次操作二可以将这一系列多个连续的1全都变成0,从而达到一步操作更改多位的效果。
而又由于这种操作二所带来的多位变化必须从最低位开始,因此注定这种操作只能使用一次。
那么,本题的策略当然就是巧用操作二, 通过构造从最低位起一定长度的连续1来优化速度。
例如:如果我们想构造长度为k的连续1串,那么总的策略即可经过以下六步即可完成:
1.通过操作一将0~(k-1)位中所有的0都变为1,从而完成构造
2.若第k位为1,需要对这一位使用一次操作1使其变为0,防止构造的连续1的串超长
3.通过一次操作2将0~(k-1)位中都变为0,将第k位变成1
4.检查目标串中的0~(k-1)位,通过操作一将实现对应的1
5.检查目标串中的第k位,若为0,则需要使用一次操作一来使该位与目标串相同
6.检查(k+1)位直至末尾所有位,若有不相同的位则利用操作一变换完成
其实不难发现,策略一,策略四和策略六都可以通过预处理实现
而策略二,策略三和策略五都仅通过一步就可以完成
于是,我们只需要枚举所需构造的连续1串的长度k即可
最后注意两种特殊情况:
1.暴力全使用操作一就是最优策略,不需要使用操作二,这种情况可以在初始化的时候就直接考虑掉
2.需要构造的连续1串的长度和原串相同,即策略五不需要再执行,这里我们可以在初始化的过程中,将策略五多初始化两位并定位0,即可实现。此外,这种情况下,操作二的进位会在第n位,比原串长,这就是为什么原题目并没有直接说串长为n,而说的是无限长串第n位及以后均为0,这就需要我们手动补0将两串都延长一位。
代码:
/*
Author Owen_Q
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
char s[maxn],t[maxn];
int diff[maxn],chag[maxn];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
//freopen(".txt","r",stdin);
//ios::sync_with_stdio(false);
//cin.tie(0);
scanf("%s%s",s,t);
diff[n] = 0;
diff[n+1] = 0;
for(int i=n-1;i>=0;i--)
{
diff[i] = diff[i+1];
if(s[i]!=t[i])
diff[i]++;
}
t[n] = '0';
s[n] = '0';
chag[0] = 0;
if(s[0]=='0')
chag[0]++;
if(t[0]=='1')
chag[0]++;
/*if(t[1]=='0')
chag[0]++;
*/
for(int i=1;i<n;i++)
{
chag[i] = chag[i-1];
if(s[i]=='0')
chag[i]++;
if(t[i]=='1')
chag[i]++;
/*else
chag[i]--;
if(t[i+1]=='0')
chag[i]++;*/
}
int re = diff[0];
for(int i=0;i<n;i++)
{
/*if(s[i+1]=='1')
continue;*/
int now = chag[i] + diff[i+2] + 1;
if(t[i+1]=='0')
now++;
if(s[i+1]=='1')
now++;
re = re>now?now:re;
}
//re = re>chag[n-1]?chag[n-1]:re;
printf("%d\n",re);
/*for(int i=0;i<n;i++)
cout << diff[i];
cout << endl;
for(int i=0;i<n;i++)
cout << chag[i];
cout << endl;*/
}
return 0;
}
/*
注意进位后下一位的状况
预处理防止连续进位
*/