P7119 Mivik 的游戏
标签:思维,线段树
题意:给定n枚硬币排成一排,现在进行一种操作:如果现在硬币中有k枚硬币反面朝上,那么翻转从左到右数第k个硬币,不断进行这种操作,直到没有硬币反面朝上。
给出m次修改,每次操作会翻转[l, r]区间的硬币,请回答修改前和每次修改后将所有硬币变为正面向上所需次数,若不能使所有硬币正面向上,输出“never”。
输入时会输入一个字符串,若第i个字符是“H”,表示第i个硬币正面朝上,若第i个字符是“T”,表示第i个硬币反面朝上。
题解
先来思考一下这个操作是个什么过程,我们知道进行操作的位置和目前反面朝上的硬币数有关,记当前反面朝上的硬币数为k,我们模拟一下操作过程会发现,整个过程实际上就是从位置k开始一直向后翻转硬币,直到遇到第一个反面朝上的硬币,记位置为pos,然后再倒着翻转硬币直到回到位置k,整个过程的操作次数为2 * pos - 2 * k + 1,这个过程我们实现的结果实际上是把一个反面朝上的硬币变成了正面朝上,接下来反面朝上的硬币数就变成了k - 1,然后重复上面的操作,最终就能将所有硬币变为正面朝上,所以实际上不存在无法结束的情况。
接下来我们看看总的操作次数如何计算,由上面的分析可知,一共有k个反面朝上的硬币,而每将一个反面朝上的硬币翻转过来需要花费2 * pos[i] - 2 * k + 1步,那么将所有反面朝上的硬币都翻转过来一共就需要2 * (pos[1] + pos[2] + … + pos[k]) - 2 * (1 + 2 + … + k) + k * 1 = 2 * (pos[1] + pos[2] + … + pos[k]) - k * k,其中pos[i]表示反面朝上的硬币的位置。
开始先将字符串处理一下,正面朝上赋值为0,反面朝上赋值为1。上面推出的式子中所用到的两个值和区间异或有关,我们可以利用线段树来维护,设置两个数组cnt,sum分别储存区间反面朝上的硬币数以及区间反面朝上的硬币的下标和,和普通线段树不同的地方其实就在于更新,标记数组的修改只需要异或1即可,cnt数组的修改,其实就是变成了原来0的个数,即区间长度r - l + 1 - 原来1的个数cnt[u],sum数组的修改和cnt类似,即区间下标和(r - l + 1)* (l + r) / 2 - 原来1的下标和sum[u]
代码实现
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <math.h>
#define ll long long
#define fst ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int N = 1e6 + 50;
int n, m;
ll sum[N << 2], cnt[N << 2];
int tag[N << 2], a[N];
string s;
void pushup(int u){
cnt[u] = cnt[u << 1] + cnt[u << 1 | 1];
sum[u] = sum[u << 1] + sum[u << 1 | 1];
}
void build(int u, int l, int r){
if(l == r){
if(a[l]){
cnt[u] = 1;
sum[u] = l;
}
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void update(int u, int l, int r){
tag[u] ^= 1;
cnt[u] = r - l + 1 - cnt[u];
sum[u] = (ll)(r - l + 1) * (l + r) / 2 - sum[u];
}
void pushdown(int u, int l, int r){
if(tag[u]){
int mid = (l + r) >> 1;
update(u << 1, l, mid);
update(u << 1 | 1, mid + 1, r);
tag[u] = 0;
}
}
void modify(int u, int l, int r, int ul, int ur){
if(ul <= l && ur >= r){
update(u, l, r);
return;
}
pushdown(u, l, r);
int mid = (l + r) >> 1;
if(ul <= mid) modify(u << 1, l, mid, ul, ur);
if(ur > mid) modify(u << 1 | 1, mid + 1, r, ul, ur);
pushup(u);
}
int main(){
fst;
cin >> n >> m >> s;
for(int i = 0; i < s.size(); i++){
if(s[i] == 'T') a[i + 1] = 1;
}
build(1, 1, n);
cout << 2 * sum[1] - cnt[1] * cnt[1] << "\n";
while(m--){
int l, r;
cin >> l >> r;
modify(1, 1, n, l, r);
cout << 2 * sum[1] - cnt[1] * cnt[1] << "\n";
}
return 0;
}