题解:P14258 好感(favor)
提供一种非常规做法。
问题:
题目大意:给定一个 01 串
思路:
Step1:
对于一个
Step2:
对于每个
Step3:
对于每个
- 保持不变。
- 将其进行取反操作并把
1 \sim i 的元素向前移动。
因为我们是倒序遍历,那么每个不需要修改的元素或是已经修改的元素都不会影响到我们后续到操作,所以可以直接删去,因此可以保证每次遍历的
Step4:
我们可以总结每次操作的总体流程为:
- 将
a_i 进行取反操作。 - 将
1 \sim i 内所有下标减一。 - 将
a_0 移动到a_i 位置。 - 统计代价
i 。
观察每个流程,可以发现:
第一步可以看成将
第二步可以看作将
第三步可以看作将第一个元素移动到末尾(因为我们保证了当前遍历的
第四步可以看作获取 01 串的长度(因为我们保证了当前遍历的
综合起来发现,每一个操作都符合队列的基本操作!!!那么现在就要请出我们的
实现方式:
定义一个双端队列
- 若不是,花费
1 的代价(队首元素下标为1 )将队首元素改变。 - 否则,判断队尾元素是否需要操作:
- 若需要操作:将队尾弹出,重新插入想要的值(对应取反操作);将队首元素弹出(此时队内其他元素自动向前移动一格),插入到队尾。将答案加上代价即队列长度。
- 若不需要:直接弹出即可,因为我们后续不可能再遍历到它了。 若所有元素均已修改完成,队列中所有值均会弹出,停止循环,此时统计的代价即最小代价。
特别的,因为我们不知道究竟要把所有的
代码实现:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
const int N = 1e6 + 10;
ll t,a[N],n,b[N];
deque<ll>q;
inline ll c1(){//将所有1转换为0的最小化费
q.clear();
ll ans = 0;
for(int i = 1;i <= n;i ++)q.push_back(a[i]);
while(!q.empty()){
if(q.front() == 1)q.pop_front(),q.push_front(0),ans ++;//若队首元素不是我们想要的 修改
if(q.back() == 0)q.pop_back();//若不用修改 从队尾弹出即可
else {ans += q.size();q.pop_front();q.pop_back();}//等价于 ans += q.size();int x = q.front();q.pop_front();q.pop_back();q.push_back(0);q.push_back(x);
}
return ans;
}
inline ll c0(){//将所有0转换为1的最小化费
q.clear();//多测不清空,爆零两行泪
ll ans = 0;
for(int i = 1;i <= n;i ++)q.push_back(a[i]);
while(!q.empty()){
if(q.front() == 0)q.pop_front(),q.push_front(1),ans ++;
if(q.back() == 1)q.pop_back();
else {ans += q.size();q.pop_front();q.pop_back();}
}
return ans;
}
inline void solve(){
cin >> n;
string s;
cin >> s;
int pos = 0;
for(auto x : s){pos ++,a[pos] = x - '0';}
cout << (ll)min(c1(),c0()) << endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin >> t;
while(t --)solve();
return 0;
}