【题解】 作诗 序列分块 Luogu4135
Legend
给定长度为 \(n\) 的序列 \(a_i\),区间在线询问 \(m\) 次出现次数为偶数的数字个数(0 次不算)。
强制在线。\(1 \le n ,a_i,m \le 10^5\)。
Editorial
好像我的做法比较阴间。
考虑分块预处理 \(val_{i,j}\) 表示第 \(i\) 个块到第 \(j\) 个块的答案。
如何处理散块?预处理 \(back_{i,v}\) 表示第 \(i\) 个块及以后数字 \(v\) 从左到右第一次出现的位置。
\(front_{i,v}\) 表示第 \(i\) 个块及以后数字 \(v\) 从右到左第一次出现的位置。
再记录一下 \(where_i\) 每一个位置上的数字是这个数字从左到右第几个。
这样通过查询块内最靠左/右的数字,用 \(where\) 作差,就可以判断出来整块内的数字出现的是偶数还是奇数次了。散块就暴力更新就行了。
复杂度 \(O(n\sqrt{n})\)。
然而在写这篇题解的时候发现好像可以直接前缀和求出奇偶性(
Code
#include <bits/stdc++.h>
const int MX = 1e5 + 7;
const int SIZE = 400;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
int a[MX] ,buc[MX];
int VAL[433][433];
int occb[433][MX];
int occf[433][MX];
// occb[i][j] 表示在块 i 及以后 j 第一次出现的位置
// occf 则是表示之前最后一次出现的位置
int begin[MX] ,end[MX] ,block[MX] ,where[MX];
std::vector<int> app[MX];
int Query(int l ,int r){
int ans = 0;
if(block[r] - block[l] <= 1){
for(int i = l ; i <= r ; ++i){
buc[a[i]] += 1;
if((buc[a[i]] & 1) == 0) ++ans;
else if(buc[a[i]] != 1) --ans;
}
for(int i = l ; i <= r ; ++i) buc[a[i]] = 0;
return ans;
}
ans = VAL[block[l] + 1][block[r] - 1];
for(int i = end[block[l]] ; i >= l ; --i){
if(buc[a[i]]){
++buc[a[i]];
if(buc[a[i]] & 1) --ans;
else ++ans;
}
else{
int now = where[occf[block[r] - 1][a[i]]] - where[i];
buc[a[i]] = now + 1;
if((buc[a[i]] & 1) == 0) ++ans;
else if(buc[a[i]] != 1) --ans;
}
}
for(int i = begin[block[r]] ; i <= r ; ++i){
if(buc[a[i]]){
++buc[a[i]];
if(buc[a[i]] & 1) --ans;
else ++ans;
}
else{
int now = where[i] - where[occb[block[l] + 1][a[i]]];
buc[a[i]] = now + 1;
if((buc[a[i]] & 1) == 0) ++ans;
else if(buc[a[i]] != 1) --ans;
}
}
for(int i = l ; i <= end[block[l]] ; ++i) buc[a[i]] = 0;
for(int i = begin[block[r]] ; i <= r ; ++i) buc[a[i]] = 0;
return ans;
}
int main(){
int n = read() ,c = read() ,m = read();
for(int i = 1 ; i <= n ; ++i){
a[i] = read();
where[i] = app[a[i]].size();
app[a[i]].push_back(i);
// printf("where[%d] = %d\n" ,i ,where[i]);
}
for(int i = 0 ,bl = 0 ; i <= n ; ++i){
block[i] = bl;
if(i % SIZE == 0 || i == n){
end[bl] = i;
begin[++bl] = i + 1;
}
}
memset(occb ,0x3f ,sizeof occb);
memset(occf ,0x3f ,sizeof occf);
for(int i = n ; i ; --i){
if(i == end[block[i]]){
for(int j = 0 ; j <= c ; ++j){
occb[block[i]][j] = occb[block[i] + 1][j];
}
}
occb[block[i]][a[i]] = i;
}
for(int i = 1 ; i <= n ; ++i){
if(i == begin[block[i]]){
for(int j = 0 ; j <= c ; ++j){
occf[block[i]][j] = occf[block[i] - 1][j];
}
}
occf[block[i]][a[i]] = i;
}
for(int i = 1 ; i <= n ; i += SIZE){
int ans = 0;
for(int j = i ; j <= n ; ++j){
buc[a[j]] += 1;
if((buc[a[j]] & 1) == 0){
++ans;
}
else if(buc[a[j]] != 1) --ans;
VAL[block[i]][block[j]] = ans;
}
for(int j = i ; j <= n ; ++j) buc[a[j]] = 0;
}
int Online = 1 ,la = 0;
while(m--){
int l = read() ,r = read();
if(Online){
l = (l + la) % n + 1;
r = (r + la) % n + 1;
if(l > r) std::swap(l ,r);
}
printf("%d\n" ,la = Query(l ,r));
}
}