洛谷P4135 作诗(不一样的分块)
题面
给定一个长度为 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) 。