题面

给定一个长度为

n

n

n 的整数序列

A

A

A ,序列中每个数在

[

1

,

c

]

[1,c]

[1,c] 范围内。有

m

m

m 次询问,每次询问查询一个区间

[

l

,

r

]

[l,r]

[l,r],问有多少个数在该区间中出现了偶数次,强制在线。

n

,

m

,

c

1

0

5

n,m,c\leq 10^5

n,m,c≤105.

题解

这道题的大方向是分块。

第一个做法是时间复杂度标准

O

(

n

n

)

O(n\sqrt n)

O(nn

​) 的做法。

预处理分块数组

有很多种预处理方法,笔者就介绍一种吧(给后面的时间复杂度更优做法腾出空间)

我们令

f

[

i

]

[

j

]

f[i][j]

f[i][j] 为从块

i

i

i 开始到数列第

j

j

j 个元素的答案,令

c

[

i

]

[

j

]

c[i][j]

c[i][j] 为从块

i

i

i 开始,直到结束,数字

j

j

j 出现的次数。

这两者都很好预处理。然后询问的时候,先把

f

[

b

e

l

o

n

g

[

l

]

+

1

]

[

r

]

f[belong[l]+1][r]

f[belong[l]+1][r] 算进答案,再处理最左边的尾巴剩的那

O

(

n

)

O(\sqrt n)

O(n

​) 个数。每个数的出现次数可以通过

c

[

b

e

l

o

n

g

[

l

]

+

1

]

c

[

b

e

l

o

n

g

[

r

]

]

c[belong[l]+1]-c[belong[r]]

c[belong[l]+1]−c[belong[r]] 再加上块

b

e

l

o

n

g

[

r

]

belong[r]

belong[r] 内枚举求出(后者得先处理,不然复杂度不对)。

很经典啊,但是我看了看评测记录,大多数人用这种分块跑了 4s +。

不够优啊。于是笔者想出了一种复杂度"更优"的做法。

数据结构分块讨论

设块大小为

B

B

B ,

出现次数大于

B

B

B 的数字不超过

n

B

\frac{n}{B}

Bn​ 个,我们把这些数字出现次数的奇偶性状态全部用一个长度为

n

B

\frac{n}{B}

Bn​ 的 bitset 表示,然后直接做前缀异或,再随便(用可持久化线段树)维护任意区间出现数字的种类数。这部分贡献的时间复杂度

O

(

n

n

64

B

)

O(n\frac{n}{64B})

O(n64Bn​) 。

出现次数小于等于

B

B

B 的数,就用可持久化线段树暴力维护。维护任意区间的答案,每次把当前位置

R

=

i

R=i

R=i 前方与其相等的数都拿出来,令其位置为

b

1

,

b

2

,

.

.

.

,

b

k

b_1,b_2,...,b_k

b1​,b2​,...,bk​,然后该数在

L

(

b

k

1

,

b

k

]

L\in(b_{k-1},b_k]

L∈(bk−1​,bk​] 产生新贡献(这时

[

L

,

R

]

[L,R]

[L,R] 内刚好有两个数等于

a

i

a_i

ai​) ,在

L

(

0

,

b

1

]

,

(

b

1

,

b

2

]

,

.

.

.

,

(

b

k

2

,

b

k

1

]

L\in(0,b_1],(b_1,b_2],...,(b_{k-2},b_{k-1}]

L∈(0,b1​],(b1​,b2​],...,(bk−2​,bk−1​] 的贡献分别取反。时间复杂度

O

(

n

log

n

B

)

O(n\log n\cdot B)

O(nlogn⋅B) 。

设置一个合理的

B

B

B ,可以使时间复杂度达到

O

(

n

n

log

n

64

)

O(n\sqrt{n\cdot \frac{\log n}{64}})

O(nn⋅64logn​

​),常数相当,理论上更优。

终于跑进了 4s 内(3.87 s)

CODE

数据结构分块讨论的代码:

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<regex>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define BI bitset<10001>
#define SQ 10
LL read() {
LL f=1,x=0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f*x;
}
void putpos(LL x) {
if(!x) return ;
putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
if(!x) putchar('0');
else if(x < 0) putchar('-'),putpos(-x);
else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}
int n,m,s,o,k;
BI sm[MAXN];
int pr[MAXN];
struct it{
int ls,rs;
int nm;
it(){ls=rs=nm=0;}
}tr[MAXN*64];
int cnt,rt[MAXN];
int addtr(int a,int l,int r,int al,int ar,int y) {
if(l > r || al > r || ar < l) return a;
tr[++ cnt] = tr[a]; a = cnt;
if(al >= l && ar <= r) {tr[a].nm += y;return a;}
int md = (al + ar) >> 1;
tr[a].ls = addtr(tr[a].ls,l,r,al,md,y);
tr[a].rs = addtr(tr[a].rs,l,r,md+1,ar,y);
return a;
}
int findtr(int a,int x,int al,int ar) {
if(al > x || ar < x || !a) return 0;
if(al == ar) return tr[a].nm;
int md = (al + ar) >> 1;
return tr[a].nm + findtr(tr[a].ls,x,al,md) + findtr(tr[a].rs,x,md+1,ar);
}
vector<pair<int,int>> tre[MAXN<<2];
int M;
void maketree(int n) {
M = 1;while(M < n+2)M <<= 1;
for(int i = 1;i <= M*2-1;i ++) {
tre[i].clear(); tre[i].push_back(make_pair(0,0));
}return ;
}
void ins(int s,int y,int tm) {
int ls = tre[s].back().SE;
if(tre[s].back().FI == tm) tre[s].pop_back();
tre[s].push_back(make_pair(tm,y+ls));
}
void addtree(int l,int r,int y,int tm) {
if(l > r) return ;
int s = M+l-1,t = M+r+1;
while(s || t) {
if((s>>1) != (t>>1)) {
if(!(s&1)) ins(s^1,y,tm);
if(t & 1) ins(t^1,y,tm);
}else break;
s >>= 1;t >>= 1;
}return ;
}
int findtree(int x,int tm) {
int s = M+x,as = 0;
while(s) as += tre[s][upper_bound(tre[s].begin(),tre[s].end(),make_pair(tm,0x7f7f7f7f))-tre[s].begin()-1].SE,s >>= 1;
return as;
}
int ct[MAXN],a[MAXN];
int po[MAXN],cn;
vector<int> bu[MAXN];
int main() {
n = read();m = read();int T = read();
for(int i = 1;i <= n;i ++) {
a[i] = read();
ct[a[i]] ++;
}
for(int i = 1;i <= m;i ++) {
if(ct[i] > SQ) {
po[i] = ++ cn;
}
}
rt[0] = 0;
for(int i = 1;i <= n;i ++) {
rt[i] = rt[i-1];
if(po[a[i]]) {
sm[i][po[a[i]]] = 1;
rt[i] = addtr(rt[i],pr[a[i]] + 1,i,1,n,1);
pr[a[i]] = i;
}
sm[i] ^= sm[i-1];
}
maketree(n);
for(int i = 1;i <= n;i ++) {
int x = a[i];
if(!po[x]) {
for(int j = (int)bu[x].size()-1,ct=1;j >= 0;j --,ct++) {
int y = bu[x][j],pre = (j == 0 ? 0:bu[x][j-1]);
if(ct & 1) addtree(pre+1,y,1,i);
else addtree(pre+1,y,-1,i);
}
bu[x].push_back(i);
}
}
int las = 0;
while(T --) {
s = read();o = read();
s = (s + las) % n + 1;
o = (o + las) % n + 1;
if(s > o) swap(s,o);
las = findtree(s,o);
BI as = sm[o] ^ sm[s-1];
las += findtr(rt[o],s,1,n) - (int)as.count();
AIput(las,'\n');
}
return 0;
}

在这之后,我又打了一份第一个做法的代码,无优化交上去 2.90 秒 Rank#1

蚌埠住了

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<regex>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define SQ 320
LL read() {
LL f=1,x=0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f*x;
}
void putpos(LL x) {
if(!x) return ;
putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
if(!x) putchar('0');
else if(x < 0) putchar('-'),putpos(-x);
else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}
int n,m,s,o,k;
int a[MAXN],nx[MAXN],ps[MAXN];
int cn,bl[MAXN];
int f[SQ+2][MAXN],ct[MAXN];
int c[SQ+2][MAXN];
int main() {
n = read();m = read();int T = read();
for(int i = 1;i <= n;i ++) {
a[i] = read();
int B = i/SQ+1;
if(!bl[B]) bl[B] = i;
cn = B;
}
for(int i = 1;i <= m;i ++) ps[i] = n+1;
for(int i = n;i > 0;i --) {
nx[i] = ps[a[i]];
ps[a[i]] = i;
}
for(int i = 1;i <= cn;i ++) {
for(int j = bl[i];j <= n;j ++) {
f[i][j] = f[i][j-1];
int x = a[j];
c[i][x] ++;
if(c[i][x] > 1) {
if(c[i][x] & 1) f[i][j] --;
else f[i][j] ++;
}
}
}
int las = 0;
while(T --) {
s = read();o = read();
s = (s + las) % n + 1;
o = (o + las) % n + 1;
if(s > o) swap(s,o);
int ll = s/SQ+1,rr = o/SQ+1;
las = 0;
if(ll == rr) {
for(int i = s;i <= o;i ++) {
ct[a[i]] ++;
}
for(int i = s;i <= o;i ++) {
if(ct[a[i]] > 0 && ct[a[i]]%2==0) las ++;
ct[a[i]] = 0;
}
}
else {
las = f[ll+1][o];
for(int i = s;i/SQ+1 == ll;i ++) {
ct[a[i]] = c[ll+1][a[i]] - c[rr][a[i]];
}
for(int i = o;i/SQ+1 == rr;i --) {
ct[a[i]] ++;
}
for(int i = bl[ll+1]-1;i >= s;i --) {
int x = a[i];
ct[x] ++;
if(ct[x] > 1) {
if(ct[x] & 1) las --;
else las ++;
}
}
for(int i = s;i < bl[ll+1];i ++) {
ct[a[i]] = 0;
}
for(int i = o;i/SQ+1 == rr;i --) {
ct[a[i]] = 0;
}
}
AIput(las,'\n');
}
return 0;
}

于是我才发现,由于第二个做法处理

B

\leq B

≤B 的数时,用朴素的可持久化线段树空间开不下,于是我用了基于vector的可持久化数组,空间只用了 1/3 。缺点就是,查询的复杂度变成了

O

(

log

2

n

)

O(\log^2n)

O(log2n) ,成为复杂度瓶颈,于是总复杂度变成

O

(

m

log

2

n

)

O(m\log^2n)

O(mlog2n) 。

洛谷P4135 作诗(不一样的分块)的更多相关文章

  1. 洛谷P4135 作诗 (分块)

    洛谷P4135 作诗 题目描述 神犇SJY虐完HEOI之后给傻×LYD出了一题: SHY是T国的公主,平时的一大爱好是作诗. 由于时间紧迫,SHY作完诗之后还要虐OI,于是SHY找来一篇长度为N的文章 ...

  2. 洛谷 P4135 作诗 题解

    题面. 之前做过一道很类似的题目 洛谷P4168蒲公英 ,然后看到这题很快就想到了解法,做完这题可以对比一下,真的很像. 题目要求区间内出现次数为正偶数的数字的数量. 数据范围1e5,可以分块. 我们 ...

  3. 洛谷P4135 作诗

    题意:[l,r]之间有多少个数出现了正偶数次.强制在线. 解:第一眼想到莫队,然后发现强制在线...分块吧. 有个很朴素的想法就是蒲公英那题的套路,做每块前缀和的桶. 然后发现这题空间128M,数组大 ...

  4. 洛谷 P4135 作诗

    分块大暴力,跟区间众数基本一样 #pragma GCC optimize(3) #include<cstdio> #include<algorithm> #include< ...

  5. 洛谷 P4135 作诗(分块)

    题目链接 题意:\(n\) 个数,每个数都在 \([1,c]\) 中,\(m\) 次询问,每次问在 \([l,r]\) 中有多少个数出现偶数次.强制在线. \(1 \leq n,m,c \leq 10 ...

  6. 洛谷P4135 Ynoi2016 掉进兔子洞 (带权bitset?/bitset优化莫队 模板) 题解

    题面. 看到这道题,我第一反应就是莫队. 我甚至也猜出了把所有询问的三个区间压到一起处理然后分别计算对应询问答案. 但是,这么复杂的贡献用什么东西存?难道要开一个数组 query_appear_tim ...

  7. Bzoj2002/洛谷P3203 [HNOI2010]弹飞绵羊(分块)

    题面 Bzoj 洛谷 题解 大力分块,分块大小\(\sqrt n\),对于每一个元素记一下跳多少次能跳到下一个块,以及跳到下一个块的哪个位置,修改的时候时候只需要更新元素所在的那一块即可,然后询问也是 ...

  8. P4135 作诗——分块

    题目:https://www.luogu.org/problemnew/show/P4135 分块大法: 块之间记录答案,每一块记录次数前缀和: 注意每次把桶中需要用到位置赋值就好了: 为什么加了特判 ...

  9. luogu P4135 作诗

    嘟嘟嘟 郑重声明:我的前几到分块题写法上都有点小毛病,以这篇为主! 这道题感觉也是分块的基本套路,只不过卡常,得开氧气. 维护俩:sum[i][j]表示前 i 块中,数字 j 出现了多少次,ans[i ...

随机推荐

  1. c++ 关于二分的STL 详解

    一.解释 以前遇到二分的题目都是手动实现二分,不得不说错误比较多,关于返回值,关于区间的左闭右开等很容易出错,最近做题发现直接使用STL中的二分函数方便快捷还不会出错,不过对于没有接触过的同学,二分函 ...

  2. Ubuntu Linux处理Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 3365 (unattended-upgr)问题

    问题 在Ubuntu中,执行apt install后,出现以下问题: Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-fro ...

  3. 百度地图API 地图圈区域并计算坐标点是否在区域内

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. JS:构造函数

    定义:在JavaScript中,用new关键字来调用的函数,称为构造函数,构造函数首字母一般大写. 理解: 构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象. 创建对象, ...

  5. React技巧之组件中返回多个元素

    原文链接:https://bobbyhadz.com/blog/react-return-multiple-elements 作者:Borislav Hadzhiev 正文从这开始~ fragment ...

  6. 由ASP.NET Core根据路径下载文件异常引发的探究

    前言 最近在开发新的项目,使用的是ASP.NET Core6.0版本的框架.由于项目中存在文件下载功能,没有使用类似MinIO或OSS之类的分布式文件系统,而是下载本地文件,也就是根据本地文件路径进行 ...

  7. 简单到爆——用Python在MP4和GIF间互转,我会了

    写在前面的一些P话: 昨天用公众号写文章的时候,遇到个问题.我发现公众号插入视频文件太繁琐,一个很小的视频,作为视频传上去平台还要审核,播放的时候也没gif来的直接.于是想着找个工具将mp4转换成gi ...

  8. CANN算子:利用迭代器高效实现Tensor数据切割分块处理

    摘要:本文以Diagonal算子为例,介绍并详细讲解如何利用迭代器对n维Tensor进行基于位置坐标的大批量数据读取工作. 本文分享自华为云社区<CANN算子:利用迭代器高效实现Tensor数据 ...

  9. 腾讯云数据库公有云市场稳居TOP 2!

    7月4日,国际权威机构IDC发布的<2021年下半年中国关系型数据库软件市场跟踪报告>显示,腾讯云数据库在关系型数据库软件市场(公有云模式)中,位列第二. IDC报告显示,2021下半年中 ...

  10. MyBatis 映射文件

    Mybatis映射文件简介 1) MyBatis 的真正强大在于它的映射语句.由于它的异常强大,映射器的 XML 文件就显得相对简单.如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉 ...