洛谷P7119 Mivik 的游戏

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值