树套树

“没有独自完成的赞歌,没有携手共进的归冢。”

顾名思义,由于一些神奇的原因,我们将一个数据结构维护的每一个点都建立另一个数据结构以达到维护问题答案的操作。一般而言分为外层树内层树

外层树一般有线段树和树状数组;
内层树一般有平衡树,线段树和 $\text{Stl}$ 。


树链剖分

其实树剖就是树套树这个思想的前身。因为树剖是树链套数据结构。

其中的外层树就是树链,内层树就是所需要的数据结构。而树套树也就需要这个思想,但外层树不仅仅是树链,而是另一种所需的数据结构。


线段树套 Stl

维护区间前继与修改操作:

写出一种数据结构,可维护以下操作:

  1. 把第 $i$ 个数修改成 $x$ ;
  2. 求 $x$ 在区间 $[l,r]$ 内的前继。

对于单点修改和区间查询,我们可以很容易想到线段树,而对前继操作,则考虑 std::set 或者 std::multiset

不用担心空间复杂度的条件,因为线段树的性质,std::set 的个数不会超过 $\mathcal O(n\log n)$ (虽然听起来也挺多的了)。

对于操作一,我们可以在原来的 std::set 中删去原数,并添加上 $x$ ,总共会修改 $\mathcal O(\log n)$ 个区间,一次修改的时间复杂度为 $\mathcal O(\log^2 n)$ 。

对于操作二,直接在区间中使用 std::set 中的 lower_boundupper_bound 即可。

P5838

这道题的做法出奇的多,不过总归到底还是一个树链剖分的练习。

正解有什么 $\operatorname{Tarjan}$ ,主席树,智慧法(神仙离线线性算法),分块,莫队等等。

但给一个懒人打法:

树剖套线段树套平衡树(虽然码量大)

首先,用树剖维护路径并对每一条链建立一个线段树,线段树维护 $dfn[]$ 序区间的颜色出现情况,用 std::map 或者 std::set 来维护。

因为本人过菜不会使用 std::set 所以选择了更为费时但简单的 std::map

每次查询就 $\operatorname{or}$ 上当前区间的 $Map[c]$ ,当 $res\ne 0$ 时直接判断 $\text{true}$ 。

但总之,$\operatorname{Stl}$ 的存在就已经证明了这个 $\mathcal O(n\log^3 n)$ 的方法不可过,所以直接吸氧,然后解决。现在大部分的比赛都会自带 $\operatorname{O_2}$ 优化,所以不必过于担心。

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<iomanip>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define gh() getchar()
#define re register
typedef long long ll;
const int MAXN=1e5+10;
template<class T>
inline void read(T &x)
{
x=0;
char ch=gh(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gh();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<class T>
inline bool checkMax(T &x,T &y)
{
return x<y?x=y,1:0;
}
template<class T>
inline bool checkMin(T &x,T &y)
{
return x>y?x=y,1:0;
}
int N,Q,T[MAXN];
struct G
{
int next,to;
}Edge[MAXN<<1];
int Head[MAXN],Total;
inline void addEdge(int u,int v)
{
Edge[++Total]=(G){Head[u],v};Head[u]=Total;
Edge[++Total]=(G){Head[v],u};Head[v]=Total;
}
int Fa[MAXN],Son[MAXN],Dep[MAXN],Siz[MAXN];
void dfsTree(int x,int last)
{
Fa[x]=last,Dep[x]=Dep[last]+1,Siz[x]=1;
for(int e=Head[x],v;e;e=Edge[e].next)
{
if((v=Edge[e].to)==last) continue;
dfsTree(v,x);
Siz[x]+=Siz[v];
if(!Son[x]||Siz[v]>Siz[Son[x]]) Son[x]=v;
}
}
int Top[MAXN],Dfn[MAXN],Bck[MAXN],Cnt;
void dfsChain(int x,int topf)
{
Top[x]=topf,Dfn[x]=++Cnt;
Bck[Cnt]=x;
if(!Son[x]) return ;
dfsChain(Son[x],topf);
for(int e=Head[x],v;e;e=Edge[e].next)
{
if((v=Edge[e].to)==Fa[x]||v==Son[x]) continue;
dfsChain(v,v);
}
}
#define ls p<<1
#define rs p<<1|1
struct ST
{
int l,r;
std::map<int,bool>Mp;
}Tree[MAXN<<2];
void build(int p,int l,int r)
{
Tree[p].l=l,Tree[p].r=r;
for(int i=l;i<=r;++i) Tree[p].Mp[T[Bck[i]]]=1;
if(l==r) return ;
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
}
bool queryT(int p,int l,int r,int T)
{
if(l<=Tree[p].l&&Tree[p].r<=r) return Tree[p].Mp[T];
int mid=(Tree[p].l+Tree[p].r)>>1,res=0;
if(l<=mid) res|=queryT(ls,l,r,T);
if(mid<r) res|=queryT(rs,l,r,T);
return res;
}
inline bool queryTPath(int x,int y,int T)
{
int res=0;
while(Top[x]!=Top[y])
{
if(Dep[Top[x]]<Dep[Top[y]]) std::swap(x,y);
res|=queryT(1,Dfn[Top[x]],Dfn[x],T);
if(res) return res;
x=Fa[Top[x]];
}
if(Dep[x]>Dep[y]) std::swap(x,y);
res|=queryT(1,Dfn[x],Dfn[y],T);
return res;
}
int ans[MAXN];
int main()
{
// freopen("treechain-segmenttree-std::map.in","r",stdin);
// freopen("treechain-segmenttree-std::map.out","w",stdout);
read(N,Q);
for(int i=1;i<=N;++i) read(T[i]);
for(int i=2,u,v;i<=N;++i)
{
read(u,v);addEdge(u,v);
}
dfsTree(1,0);dfsChain(1,1);
build(1,1,N);
for(int i=1,x,y,t;i<=Q;++i)
{
read(x,y,t);
ans[i]=queryTPath(x,y,t);
}
for(int i=1;i<=Q;++i) putchar(ans[i]+'0');
return 0;
}
/*
5 5
1 1 2 1 2
1 2
2 3
2 4
1 5
1 4 1
1 4 2
1 3 2
1 3 1
5 5 1
*/

线段树套平衡树

其实 std::set 本身就是一个平衡树,只是这个平衡树已经封装好了,不需要你自己来实现。而现在,我们需要根据题目定义自己实现平衡树。

二逼平衡树

二逼平衡树板子。简化一下题意:

  1. 询问区间 $k$ 排名;
  2. 询问区间排名 $k$ ;
  3. 单点修改;
  4. 查询区间前驱;
  5. 查询区间后驱。

除去区间之外,都是平衡树常见的操作,所以考虑外部线段树,内部平衡树。

最慢的操作达到 $\mathcal O(\log^3 n)$ ,所以整个问题的时间复杂度为 $\mathcal O(m\log ^3 n)$ 。

另话:洛谷里这道题似乎非常卡常,离大谱。(调个线段树套 $\text{Splay}$ 调的人心态爆炸,结果最后因为没去 freopen ???)还是开了 $\mathcal O_2$ 才过的。

码风指导:封装加 $\text{Splay}$ 。(有时间再写个线段树套 $\text{Treap}$ )

AC Code Splay ver.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<iomanip>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define gh() getchar()
#define re register
typedef long long ll;
const int MAXN=3e6+10;
const int INF=2147483647;
template<class T>
inline void read(T &x)
{
x=0;
char ch=gh(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gh();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<class T>
inline bool checkMax(T &x,T &y)
{
return x<y?x=y,1:0;
}
template<class T>
inline bool checkMin(T &x,T &y)
{
return x>y?x=y,1:0;
}
int N,Q,Val[MAXN];
namespace B_T //平衡树
{
struct Balanced
{
int siz,chi[2],fa,val;
inline void init(int _v,int _fa)
{
val=_v,fa=_fa,siz=1;
}
}Tre[MAXN<<4];
int Idx=0;
inline void pushUp(int p)
{
Tre[p].siz=Tre[Tre[p].chi[0]].siz+Tre[Tre[p].chi[1]].siz+1;
}
inline void rotate(int x)
{
int y=Tre[x].fa;
int z=Tre[y].fa;
int k=Tre[y].chi[1]==x;
Tre[z].chi[Tre[z].chi[1]==y]=x,Tre[x].fa=z;
Tre[y].chi[k]=Tre[x].chi[k^1],Tre[Tre[x].chi[k^1]].fa=y;
Tre[x].chi[k^1]=y,Tre[y].fa=x;
pushUp(y),pushUp(x);
}
inline void Splay(int &Rt,int x,int k)
{
while(Tre[x].fa!=k)
{
int y=Tre[x].fa;
int z=Tre[y].fa;
if(z!=k)
(Tre[y].chi[1]==x)^(Tre[z].chi[1]==y)?rotate(x):rotate(y);
rotate(x);
}
if(!k) Rt=x;
} //Splay经典操作
inline void insert(int &Rt,int v)
{
int u=Rt,p=0;
while(u) p=u,u=Tre[u].chi[v>Tre[u].val];
u=++Idx;
if(p) Tre[p].chi[v>Tre[p].val]=u;
Tre[u].init(v,p);Splay(Rt,u,0);
}
inline int lowerCnt(int Rt,int v)
{
int u=Rt,res=0;
while(u)
{
if(Tre[u].val<v) res+=Tre[Tre[u].chi[0]].siz+1,u=Tre[u].chi[1];
else u=Tre[u].chi[0];
}
return res;
} //查找比v小的数的个数
inline void modifyX(int &Rt,int x,int k)
{
int u=Rt;
while(u)
{
if(Tre[u].val==x) break;
if(Tre[u].val<x) u=Tre[u].chi[1];
else u=Tre[u].chi[0];
}
Splay(Rt,u,0);
int l=Tre[u].chi[0],r=Tre[u].chi[1];
while(Tre[l].chi[1]) l=Tre[l].chi[1];
while(Tre[r].chi[0]) r=Tre[r].chi[0];
Splay(Rt,l,0),Splay(Rt,r,l);
Tre[r].chi[0]=0;
pushUp(r),pushUp(l);
insert(Rt,k);
} //修改操作:删去原数,添加新数
inline int lowerNum(int Rt,int v)
{
int u=Rt,res=-INF;
while(u)
{
if(Tre[u].val<v)
res=std::max(res,Tre[u].val),u=Tre[u].chi[1];
else u=Tre[u].chi[0];
}
return res;
} //前驱
inline int upperNum(int Rt,int v)
{
int u=Rt,res=INF;
while(u)
{
if(Tre[u].val>v)
res=std::min(res,Tre[u].val),u=Tre[u].chi[0];
else u=Tre[u].chi[1];
}
return res;
} //后驱
}; //注意:函数里所有的根节点传的都是实参
namespace S_T //线段树
{
#define ls p<<1
#define rs p<<1|1
struct Segment
{
int l,r,Rt;
}Tree[MAXN<<2];
void build(int p,int l,int r)
{
Tree[p].l=l,Tree[p].r=r;
B_T::insert(Tree[p].Rt,-INF),B_T::insert(Tree[p].Rt,INF);
for(int i=l;i<=r;++i) B_T::insert(Tree[p].Rt,Val[i]);
//初始化,讨论区里好像说不需要每个Splay都开极值,
//可以判空,这样更快(500ms左右),但我懒,没写。
if(l==r) return ;
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
}
int queryCnt(int p,int l,int r,int k)
{
if(l<=Tree[p].l&&Tree[p].r<=r) return B_T::lowerCnt(Tree[p].Rt,k)-1;
int mid=(Tree[p].l+Tree[p].r)>>1,res=0;
if(l<=mid) res+=queryCnt(ls,l,r,k);
if(mid<r) res+=queryCnt(rs,l,r,k);
return res;
} //区间查找比k小的数的个数
void modifyX(int p,int x,int k)
{
B_T::modifyX(Tree[p].Rt,Val[x],k);
if(Tree[p].l==Tree[p].r) return ;
int mid=(Tree[p].l+Tree[p].r)>>1;
if(x<=mid) modifyX(ls,x,k);
else modifyX(rs,x,k);
} //修改,每个区间都要修改
int queryLower(int p,int l,int r,int v)
{
if(l<=Tree[p].l&&Tree[p].r<=r) return B_T::lowerNum(Tree[p].Rt,v);
int mid=(Tree[p].l+Tree[p].r)>>1,res=-INF;
if(l<=mid) res=std::max(res,queryLower(ls,l,r,v));
if(mid<r) res=std::max(res,queryLower(rs,l,r,v));
return res;
} //区间前驱
int queryUpper(int p,int l,int r,int v)
{
if(l<=Tree[p].l&&Tree[p].r<=r) return B_T::upperNum(Tree[p].Rt,v);
int mid=(Tree[p].l+Tree[p].r)>>1,res=INF;
if(l<=mid) res=std::min(res,queryUpper(ls,l,r,v));
if(mid<r) res=std::min(res,queryUpper(rs,l,r,v));
return res;
} //区间后驱
};
int MaxQx,MinQx=1e8;
//小优化,二分时记录当前输入的极值
//而非每次二分[0,1e8],似乎会快300ms左右
int main()
{
// freopen("tree-set.in","r",stdin);
// freopen("tree-set.out","w",stdout);
read(N,Q);
for(re int i=1;i<=N;++i) read(Val[i]);
S_T::build(1,1,N);
for(int i=1;i<=N;++i)
{
MaxQx=std::max(Val[i],MaxQx);
MinQx=std::min(MinQx,Val[i]);
}
while(Q--)
{
int opt,Ql,Qr,Qx;
read(opt);
if(opt==1)
{
read(Ql,Qr,Qx);
write(S_T::queryCnt(1,Ql,Qr,Qx)+1),puts("");
}
else if(opt==2)
{
read(Ql,Qr,Qx);
int l=MinQx,r=MaxQx,ans;
while(l<=r)
{
int mid=(l+r)>>1;
if(S_T::queryCnt(1,Ql,Qr,mid)+1<=Qx) l=mid+1,ans=mid;
else r=mid-1;
}
write(ans),puts("");
}
else if(opt==3)
{
read(Ql,Qx);
S_T::modifyX(1,Ql,Qx);
Val[Ql]=Qx;
MaxQx=std::max(Qx,MaxQx),MinQx=std::min(MinQx,Qx);
}
else if(opt==4)
{
read(Ql,Qr,Qx);
write(S_T::queryLower(1,Ql,Qr,Qx)),puts("");
}
else
{
read(Ql,Qr,Qx);
write(S_T::queryUpper(1,Ql,Qr,Qx)),puts("");
}
}
return 0;
}
/*
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
*/

分块套树状数组

这个代码似乎相对而言比较简短,容易实现。

可以用于二维平面中的矩阵区域的点数查询与修改。

例子一

给出 $n$ 个二维平面的点 $(x_i,y_i)$ ,实现操作:

  1. $opt,a,b,c,d$ ,询问左上角为 $(a,b)$ ,右下角为 $(c,d)$ 的矩阵区域内的点的个数。
  2. $opt,x,y$ ,把横坐标为 $x$ 的点的纵坐标改为 $y$ 。

强制在线,保证 $x_i\ne x_j(1\le i,j\le n,i\ne j)$ ,$1\le i\le n,1\le x_i,y_i\le n,1\le n\le 10^5$ 。

因为一个 $x$ 只对应一个 $y$ ,所以用一个数组记录这个映射关系,令 $Y[i]$ 表示横坐标为 $i$ 的纵坐标。

以 $\sqrt n$ 分块横坐标,为每一个块建立权值树状数组,记 $Tre[i]$ 为第 $i$ 个块的树状数组,则 $Tre[i][j]$ 表示块 $i$ 里纵坐标在 $\displaystyle\left(j-\operatorname{lowbit}(j),j\right]$ 内点的个数。

这是一个区间带修改的二维偏序问题(至于偏序,之后有机会再写),根据矩阵容斥,可以知道:

$[a,b,c,d]=[1,1,c,d]-[1,1,a,d]-[1,1,b,c]+[1,1,a,b]$

查询

首先暴力处理左右的非完整块区间,统计答案;然后对于完整的块,暴力扫描该块之前的所有块中 $Tre[i][b]$ 的值,即查询 $Tre$ 的前缀和,可以进行性质技巧优化。

修改

可以直接找到 $x$ 所在的块,然后 $add(y_1,1),add(y_2,-1)$ ,并赋值 $Y[x]=y$ 。

对上述操作进行优化,可以视为删点和加点。

空间复杂度

分块 $\mathcal O(\sqrt n)$ ,树状数组 $\mathcal O(n)$ ,总共 $\mathcal O(n\sqrt n)$ 。

时间复杂度

遍历非完整块段 $\mathcal O(\sqrt n)$ ,遍历完整块段 $\mathcal O(\log\sqrt n)$ ,完整块的树状数组查询 $\mathcal O(\log n)$ ,时间复杂度 $\mathcal O(\sqrt n+\log(n+\sqrt n))$ 。

补充

感谢 $\text{@reveal}$ 提供的代码,让我有了一些深入的理解。

这是在线带修二维偏序吧。

对序列分块,值域建树状数组就好了啊。

假设用朴素的方式,大概流程:

  • 读入
  • 对序列进行分块
  • 扫描每一个块,按值域大小建立树状数组
  • 将每一个块内的数字放入对应的树状数组

修改:

  • 将原来的数字从树状数组中删除
  • 将新的数字加入树状数组

查询:

  • 扫描散块,统计
  • 扫描整块,在树状数组上查找区间和(即某个值域的数字总数)

然后对于非朴素的方式,注意到复杂度并不均匀,查询时扫描整块并查询为复杂度瓶颈。

注意到这具有前缀和形式,即可以通过统计前若干块整块的答案来得到答案。

那么我们可以在修改时,对于对应点之后的整块做修改(类似于树状数组的形式),之后的查询也同样处理即可。

Example Code @reveal ver.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
using uni=unsigned;
using ll=long long;
using db=double;
const int N=100010,BMAX=350,FSIZE=1<<26;
// is 为所属块的编号,lst 为所属块的起始,nxt 为所属快的结尾
int n,q,a[N],is[N],lst[N],nxt[N];

struct FT{ // 经典树状数组
int size;
vector<int> a;
void build(int x){a.resize((size=x)+1);}
void operator+=(int x){
for(;x<=size;x+=x&-x) ++a[x];
}
void operator-=(int x){
for(;x<=size;x+=x&-x) --a[x];
}
int calc(int x){
int re=0;
for(;x;x-=x&-x) re+=a[x];
return(re);
}
int query(int x,int y){
return(calc(y)-calc(x-1));
}
}t[BMAX];

char BuF[FSIZE],*InF=BuF;
template<typename T>void read(T &x){
for(;48>*InF||*InF>57;++InF);
for(x=0;47<*InF&&*InF<58;x=x*10+(*InF++^48));
}
int main(){
fread(BuF,1,FSIZE,stdin);
read(n);

// BS 为块长,ALL 为最大可能的块的编号
const int BS=sqrt(n),ALL=n/BS+2;

// 外层树状数组添加数字
auto add=[&](int x,int y){
for(;x<=ALL;x+=x&-x) t[x]+=y;
};

// 外层树状数组删除数字
auto del=[&](int x,int y){
for(;x<=ALL;x+=x&-x) t[x]-=y;
};

// 外层树状数组求前缀
auto calc=[&](int x,int l,int r){
int re=0;
for(;x;x-=x&-x) re+=t[x].query(l,r);
return(re);
};

// 外层树状数组求区间
auto query=[&](int x,int y,int l,int r){
return(x>y?0:calc(y,l,r)-calc(x-1,l,r));
};

// 建立内层树状数组
for(int i=0;i<n/BS+1;++i) t[i].build(n);

for(int i=1,x;i<=n;++i){
read(x),read(a[i]);

// is 为所属块的编号
is[i]=(i-1)/BS+1;
add(is[i],a[i]);

// lst 为所属块的起始,nxt 为所属快的结尾
lst[i]=(is[i]-1)*BS+1;
nxt[i]=min(is[i]*BS,n);
}
read(q);
for(int t,x,y,l,r;q--;){
read(t);read(x);read(y);
if(t==0){
del(is[x],a[x]); // 删除原数
add(is[x],a[x]=y); // 加入新数
}else{
int ans=0;
read(l);read(r);

// get 为暴力处理
auto get=[&](int x,int y,int l,int r){
int re=0;
for(int i=x;i<=y;++i)
re+=l<=a[i]&&a[i]<=r;
return(re);};

ans=is[x]+1>is[y]-1?
get(x,y,l,r) // 块足够小,直接暴力处理
:
get(x,nxt[x],l,r)+ // 左端散块
get(lst[y],y,l,r)+ // 右端散块
query(is[x]+1,is[y]-1,l,r); // 整块
printf("%d\n",ans);
}
}
fclose(stdin);
fclose(stdout);
return(0);
}
/*
6
1 3
2 7
3 4
4 8
5 10
6 1
10
1 1 10 1 10
0 6 11
1 1 10 1 10
0 6 1
1 1 10 1 10
0 4 6
1 2 5 3 4
1 1 7 2 6
1 3 9 5 7
0 3 14
*/

Example Code Eternity's ver.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include<bits/stdc++.h>
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+48);
}
const int MAXN=1e5+10;
const int MAXB=3e2+50;
int N,Q,Val[MAXN],Blo,All;
inline int lowbit(int x)
{
return x&(-x);
}
struct BIT
{
int size;
std::vector<int>Tre;
inline void build(int n)
{
Tre.resize((size=n)+1);
}
void operator+=(int k)
{
for(;k<=size;k+=lowbit(k)) ++Tre[k];
}
void operator-=(int k)
{
for(;k<=size;k+=lowbit(k)) --Tre[k];
}
inline int calc(int x)
{
int res=0;
for(;x;x-=lowbit(x)) res+=Tre[x];
return res;
}
inline int query(int l,int r)
{
return calc(r)-calc(l-1);
}
}Tre[MAXB];
struct Block
{
int Blg,Lst,Nxt;
inline void init(int id)
{
Blg=(id-1)/Blo+1;
Lst=(Blg-1)*Blo+1;
Nxt=std::min(Blg*Blo,N);
}
}Blk[MAXN];
inline void add(int x,int y)
{
for(;x<=All;x+=lowbit(x)) Tre[x]+=y;
}
inline void del(int x,int y)
{
for(;x<=All;x+=lowbit(x)) Tre[x]-=y;
}
inline int calc(int x,int l,int r)
{
int res=0;
for(;x;x-=lowbit(x)) res+=Tre[x].query(l,r);
return res;
}
inline int query(int x,int y,int l,int r)
{
// std:: cout << calc(y, l, r) << " " << calc(x - 1, l, r) << '\n';
return (x>y?0:calc(y,l,r)-calc(x-1,l,r));
}
inline int get(int a,int b,int c,int d)
{
int res=0;
for(int i=a;i<=b;++i) res+=(c<=Val[i]&&Val[i]<=d);
return res;
}
int lastans;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(N);
Blo=std::sqrt(N),All=N/Blo+2;
for(int i=0;i<N/Blo+1;++i) Tre[i].build(N);
for(int i=1,x;i<=N;++i)
{
read(x,Val[i]);
Blk[i].init(i);
add(Blk[i].Blg,Val[i]);
}
read(Q);
int flag = 0;
for(int opt,a,b,c,d;Q--;)
{
read(opt,a,b);
if(opt==0)
{
del(Blk[a].Blg,Val[a]);
add(Blk[a].Blg,Val[a]=b);
}
else
{
flag ++;
// if(flag == 6)
// {
// std:: cout << "duck" << std:: endl;
// }
read(c,d);
int ans=Blk[a].Blg+1>Blk[b].Blg-1?
get(a,b,c,d):
get(a,Blk[a].Nxt,c,d)+
get(Blk[b].Lst,b,c,d)+
query(Blk[a].Blg+1,Blk[b].Blg-1,c,d);
// if(Blk[a].Blg + 1 <= Blk[b].Blg - 1) std:: cout << get(a,Blk[a].Nxt,c,d) << " " << get(Blk[b].Lst,b,c,d) << " " << query(Blk[a].Blg+1,Blk[b].Blg-1,c,d) << '\n';
write(lastans=ans),puts("");
}
// std:: cout << calc(2, 1, 10) << '\n';
}
return 0;
}
/*
6
1 3
2 7
3 4
4 8
5 10
6 1
10
1 1 10 1 10
0 6 11
1 1 10 1 10
0 6 1
1 1 10 1 10
0 4 6
1 2 5 3 4
1 1 7 2 6
1 3 9 5 7
0 3 14
6
5
6
1
3
1
*/

CF1093E Intersection of Permutations

乍一看,是一个不可做题。实际上,它确实是个不可做题

我们要查找 $b$ 数组在 $[l_b,r_b]$ 区间在 $a$ 数组的 $[l_a,r_a]$ 区间的映射。

因为 $a,b$ 都是 $n$ 的全排列,所以可以考虑 $\operatorname{Hash}$ 。

用 $Xa[i]$ 表示 $i$ 在 $a$ 中的编号,用 $Xb[i]$ 表示 $b$ 中的第 $i$ 个数在 $a$ 中的编号;构造平面直角坐标系,然后描点 $(Xa[i],Xb[i])$ ,我们就会发现,实际上的询问就是 $[l_a,l_b]$ 到 $[r_a,r_b]$ 中点的个数,然后就可以转换为上一道题。

后面的模板其实都没什么太大难度。主要是上述映射的理解和实现,非常繁琐(我想了一上午都不太清楚怎么实现)

然后直接一波拿下 $\text{11.03s}$ 的最优解第一名。

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include<bits/stdc++.h>
#define print(x) putchar(x)
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) print('-'),x=-x;
if(x>9) write(x/10);
print(x%10+48);
}
const int MAXN=2e5+10;
const int MAXB=5e2+10;
int N,Q,Xa[MAXN],Xb[MAXN];
int Bel[MAXN],Lst[MAXB],Nxt[MAXB],Num,All;
int Tre[MAXB][MAXN];
inline int lowbit(int x)
{
return x&(-x);
}
inline void build(int n)
{
All=std::sqrt(n),Num=n/All;
for(int i=1;i<=Num;++i)
{
Lst[i]=Nxt[i-1]+1;
Nxt[i]=i*All;
}
if(Nxt[Num]<n)
{
++Num;
Lst[Num]=Nxt[Num-1]+1;
Nxt[Num]=n;
}
for(int i=1;i<=Num;++i)
for(int j=Lst[i];j<=Nxt[i];++j)
Bel[j]=i;
}
inline void add(int x,int k,int val)
{
for(x=Bel[x];x<=Num;x+=lowbit(x))
for(int j=k;j<=N;j+=lowbit(j))
Tre[x][j]+=val;
}
inline int calc(int l,int r)
{
if(!l) return 0;
int res=0,id=Bel[l];
for(int i=Lst[id];i<=l;++i) res+=(Xb[i]<=r);
for(int i=id-1;i;i-=lowbit(i))
for(int j=r;j;j-=lowbit(j))
res+=Tre[i][j];
return res;
}
inline int query(int a,int b,int l,int r)
{
return (calc(r,b)-calc(r,a-1)-calc(l-1,b)+calc(l-1,a-1));
}
int main()
{
read(N,Q);
for(int i=1,v;i<=N;++i) read(v),Xa[v]=i;
for(int i=1,v;i<=N;++i) read(v),Xb[i]=Xa[v];
build(N);
for(int i=1;i<=N;++i) add(i,Xb[i],1);
for(int opt,a,b,l,r;Q--;)
{
read(opt);
if(opt==1)
{
read(a,b,l,r);
write(query(a,b,l,r)),puts("");
}
else
{
read(a,b);
add(a,Xb[a],-1),add(b,Xb[b],-1);
std::swap(Xb[a],Xb[b]);
add(a,Xb[a],1),add(b,Xb[b],1);
}
}
return 0;
}
/*
6 7
5 1 4 2 3 6
2 5 3 1 4 6
1 1 2 4 5
2 2 4
1 1 2 4 5
1 2 3 3 5
1 1 6 1 2
2 4 1
1 4 4 1 3
*/