【洛谷】P3870 开关

题目地址:

https://www.luogu.com.cn/problem/P3870

题目描述:
现有 n n n盏灯排成一排,从左到右依次编号为: 1 1 1 2 2 2,……, n n n。然后依次执行 m m m项操作。操作分为两种:
1、指定一个区间 [ a , b ] [a,b] [a,b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
2、指定一个区间 [ a , b ] [a,b] [a,b],要求你输出这个区间内有多少盏灯是打开的。
灯在初始时都是关着的。

输入格式:
第一行有两个整数 n n n m m m,分别表示灯的数目和操作的数目。
接下来有 m m m行,每行有三个整数,依次为: c c c a a a b b b。其中 c c c表示操作的种类。
c c c的值为 0 0 0时,表示是第一种操作。
c c c的值为 1 1 1时,表示是第二种操作。
a a a b b b则分别表示了操作区间的左右边界。

输出格式:
每当遇到第二种操作时,输出一行,包含一个整数,表示此时在查询的区间中打开的灯的数目。

数据范围:
对于全部的测试点,保证 2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2n105 1 ≤ m ≤ 1 0 5 1\le m\le 10^5 1m105 1 ≤ a , b ≤ n 1\le a,b\le n 1a,bn c ∈ { 0 , 1 } c\in\{0,1\} c{0,1}

思路是分块,将开着的灯视为 1 1 1,关着的视为 0 0 0。每个整块维护当前块的真实和和一个标签,即该整块需不需要翻转。修改的时候,散块暴力改,整块改标签和真实和;查询的时候,散块暴力累加,整块累加真实和。代码如下:

#include <iostream>
#include <cmath>
using namespace std;

const int N = 1e5 + 10, M = 1000;
int n, m;
int w[N], bel[N];
int len;
int sum[M], tag[M];

void modify(int l, int r) {
  int bl = bel[l], br = bel[r];
  if (bl == br)
    for (int i = l; i <= r; i++) w[i] ^= 1, sum[bl] += (w[i] ^ tag[bl]) * 2 - 1;
  else {
    int i = l, j = r;
    while (bel[i] == bl) w[i] ^= 1, sum[bl] += (w[i++] ^ tag[bl]) * 2 - 1;
    while (bel[j] == br) w[j] ^= 1, sum[br] += (w[j--] ^ tag[br]) * 2 - 1;
    for (int k = bel[i]; k <= bel[j]; k++) {
      tag[k] ^= 1;
      sum[k] = len - sum[k];
    }
  }
}

int query(int l, int r) {
  int res = 0, bl = bel[l], br = bel[r];
  if (bl == br) for (int i = l; i <= r; i++) res += w[i] ^ tag[bl];
  else {
    int i = l, j = r;
    while (bel[i] == bl) res += w[i++] ^ tag[bl];
    while (bel[j] == br) res += w[j--] ^ tag[br];
    for (int k = bel[i]; k <= bel[j]; k++) res += sum[k];
  }
  return res;
}

int main() {
  scanf("%d%d", &n, &m);
  len = sqrt(n);
  for (int i = 1; i <= n; i++) bel[i] = i / len;

  while (m--) {
    int op, l, r;
    scanf("%d%d%d", &op, &l, &r);
    if (!op) modify(l, r);
    else printf("%d\n", query(l, r));
  }
}

每次操作时间复杂度 O ( n ) O(\sqrt n) O(n ),空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值