题面

给定一个长度为

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. 13. L1,L2范数

    讲的言简意赅,本人懒,顺手转载过来:https://www.cnblogs.com/lhfhaifeng/p/10671349.html

  2. 在Visual C++ 6.0中无法使用gets()函数的解决办法

    问题 昨晚遇到一个有意思的问题,明明在Visual Studio 2019运行好好的C语言代码,Copy到Visual C++ 6.0中就无法编译通过了,错误提示信息如下: error C2143: ...

  3. JDBC:批处理

    1.批处理: 当要执行某条SQL语句很多次时.例如,批量添加数据:使用批处理的效率要高的多. 2.如何实现批处理 实践: package com.dgd.test; import java.io.Fi ...

  4. 递归概念&分类&注意事项和练习_使用递归计算1-n之间的和

    递归:方法自己调用自己 递归的分类: 递归分为两种,直接递归和间接递归 直接递归称为方法自身调用自己 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法 注意事项: 递归一定要有条件限定 ...

  5. HashMap存储自定义类型键值和LinkedHashMap集合

    HashMap存储自定义类型键值 1.当给HashMap中存放自定义对象时,如果自定义对象是键存在,保证键唯一,必须复写对象的hashCode和equals方法. 2.如果要保证map中存放的key和 ...

  6. 区间统计——ST算法

    一.引入 先举一个小栗子. 一数组有 \(n\) 个元素,有 \(m\) 次询问(\(n, m <= 10^5\)).对于每次询问给出 \(l, r\),求出 \([l, r]\)的区间和. 有 ...

  7. AI2(App Inventor 2) 离线版

    介绍 我们的目标:搭建一个本地多用户的App Inventor 2 服务器目的:课堂教学,社团活动,兴趣学习优势:管理权限(用户管理,账号切换,资源打包),网络链接速度快,拥有配套服务.注意:每次退出 ...

  8. Java开发学习(十一)----基于注解开发bean作用范围与生命周期管理

    一.注解开发bean作用范围与生命周期管理 前面使用注解已经完成了bean的管理,接下来将通过配置实现的内容都换成对应的注解实现,包含两部分内容:bean作用范围和bean生命周期. 1.1 环境准备 ...

  9. CMU15445 (Fall 2019) 之 Project#4 - Logging & Recovery 详解

    前言 这是 Fall 2019 的最后一个实验,要求我们实现预写式日志.系统恢复和存档点功能,这三个功能分别对应三个类 LogManager.LogRecovery 和 CheckpointManag ...

  10. tsconfig常用配置全解

    include, exclude, files配置项 extends配置 compilerOptions下的配置 compilerOptions.allowUnreachableCode compil ...