题目地址:
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
2≤n≤105,
1
≤
m
≤
1
0
5
1\le m\le 10^5
1≤m≤105,
1
≤
a
,
b
≤
n
1\le a,b\le n
1≤a,b≤n,
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)。