inline void Merge(int &x, int y, int l, int r)
{
if (!x || !y)
{
x = x + y;
return ;
}
int mid = l + r >> 1;
Merge(lc(x), lc(y), l, mid);
Merge(rc(x), rc(y), mid + 1, r);
sze[x] += sze[y];
deleteNode(y);
}
inline void Split(int x, int l, int r, int &a, int &b, int k)
{
if (l == r)
{
a = x;
b = 0;
return ;
}
int mid = l + r >> 1;
if (k <= cnt[lc[x]])
{
a = newNode();
b = x;
Split(lc[x], l, mid, lc[a], lc[b], k);
}
else
{
a = x;
b = newNode();
Split(rc[x], mid + 1, r, rc[a], rc[b], k - cnt[lc[x]]);
}
Update(a);
Update(b);
}
set 或一般的线段树维护连续段的信息。inline void dfs1(int x)
{
sze[x] = 1;
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (y == fa[x])
continue ;
fa[y] = x;
dep[y] = dep[x] + 1;
dfs1(y);
sze[x] += sze[y];
if (sze[y] > sze[son[x]])
son[x] = y;
}
}
inline void dfs2(int x)
{
if (son[x])
{
pos[son[x]] = ++V;
top[son[x]] = top[x];
idx[V] = son[x];
dfs2(son[x]);
}
int y;
for (arc *e = adj[x]; e; e = e->nxt)
if (!top[y = e->to])
{
pos[y] = ++V;
idx[V] = y;
top[y] = y;
dfs2(y);
}
}
inline void Init()
{
dfs1(1);
pos[1] = idx[1] = top[1] = V = 1;
dfs2(1);
}
inline int pathQuery(int u, int v)
{
int res = 0;
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
std::swap(x, y);
res += querySum(1, 1, n, pos[top[x]], pos[x]);
x = fa[top[x]];
}
if (dep[x] > dep[y])
std::swap(x, y);
return res + querySum(1, 1, n, pos[x], pos[y]);
}
实现时需注意 [ l , r ] [l,r] [l,r] 在同一块内的情况以及分块的边界问题。
区间众数。
将序列平均分成 n \sqrt n n 块,预处理 c n t [ i ] [ j ] cnt[i][j] cnt[i][j] 表示元素 i i i 在前 j j j 块中出现的次数, a n s [ i ] [ j ] ans[i][j] ans[i][j] 表示第 i i i 块到第 j j j 块的众数。
c n t [ i ] [ j ] cnt[i][j] cnt[i][j] 即先枚举 j j j 后枚举 i i i 统计, a n s [ i ] [ j ] ans[i][j] ans[i][j] 即先枚举 i i i,将第 i i i 块及其之后的数依次加入,用桶维护众数。
询问时设完整块为第 L L L 块到第 R R R 块,则答案要么为 a n s [ L ] [ R ] ans[L][R] ans[L][R],要么为非完整块中的数,暴力枚举即可。
时间复杂度 O ( n n ) \mathcal O(n \sqrt n) O(nn)。
插入删除元素,询问元素的最大值/最小值。
待补充。
允许离线,无修改,询问区间。
允许离线,带修改,询问区间。
将序列所有点分块,块的大小为 n 2 3 n^{\frac{2}{3}} n32,共有 n 1 3 n^{\frac{1}{3}} n31 个块,将询问按左端点所在块为第一关键字,右端点所在块为第二关键字,询问的时间为第三关键字进行排序,易分析出总时间复杂度为 O ( n 5 3 ) \mathcal O(n^{\frac{5}{3}}) O(n35)。
时间指针的移动即修改操作正向和逆向的进行。
for (int i = 1, l, r; i <= m; ++i)
{
char ch;
while (ch = getchar(), ch != 'R' && ch != 'Q');
if (ch == 'Q')
{
++qm;
q[qm].scan(pm, qm);
}
else
{
read(l); read(r);
p[++pm] = modify(l, _a[l], r);
_a[l] = r;
}
}
std::sort(q + 1, q + qm + 1);
int tl = 1, tr = 0, tt = 0;
for (int i = 1; i <= qm; ++i)
{
int l = q[i].l, r = q[i].r;
while (tt < q[i].t)
{
modify b = p[++tt];
if (b.x >= tl && b.x <= tr)
{
if (!--cnt[b.pre])
--ans;
if (!cnt[b.suf]++)
++ans;
}
a[b.x] = b.suf;
}
while (tt > q[i].t)
{
modify b = p[tt--];
if (b.x >= tl && b.x <= tr)
{
if (!--cnt[b.suf])
--ans;
if (!cnt[b.pre]++)
++ans;
}
a[b.x] = b.pre;
}
while (tl < l)
if (!--cnt[a[tl++]])
--ans;
while (tl > l)
if (!cnt[a[--tl]]++)
++ans;
while (tr > r)
if (!--cnt[a[tr--]])
--ans;
while (tr < r)
if (!cnt[a[++tr]]++)
++ans;
fans[q[i].id] = ans;
}
[ t l , x − 1 ] → [ t l , x ] ( t r < x ≤ r ) [tl,x - 1]\to[tl,x]\ (tr < x \le r) [tl,x−1]→[tl,x] (tr<x≤r)
记
F
(
x
,
r
)
=
f
(
x
,
1
,
r
)
F(x,r) = f(x,1,r)
F(x,r)=f(x,1,r),对答案产生的贡献为
f
(
x
,
t
l
,
x
−
1
)
=
F
(
x
,
x
−
1
)
−
F
(
x
,
t
l
−
1
)
f(x,tl,x - 1)=F(x,x - 1) - F(x, tl - 1)
f(x,tl,x−1)=F(x,x−1)−F(x,tl−1)
其中
F
(
x
,
x
−
1
)
F(x,x-1)
F(x,x−1) 很容易预处理,
F
(
x
,
t
l
−
1
)
F(x,tl - 1)
F(x,tl−1) 则可以通过扫描线将询问区间
[
t
r
+
1
,
r
]
[tr + 1,r]
[tr+1,r] 挂在
t
l
−
1
tl - 1
tl−1 处暴力询问得到。
// 假定 F(x, x) = F(x, x - 1),sum 数组为 F(x, x - 1) 的前缀和
// 以下为扫描线预处理部分,注意 ans 数组存储的只是最终答案的差分形式
for (int i = 1; i <= m; ++i)
{
int l = q[i].l, r = q[i].r;
if (tr < r)
{
v[tl - 1].push_back(segQuery(tr + 1, r, -1, i));
ans[i] += sum[r] - sum[tr];
tr = r;
}
if (tr > r)
{
v[tl - 1].push_back(segQuery(r + 1, tr, 1, i));
ans[i] -= sum[tr] - sum[r];
tr = r;
}
if (tl < l)
{
v[tr].push_back(segQuery(tl, l - 1, -1, i));
ans[i] += sum[l - 1] - sum[tl - 1];
tl = l;
}
if (tl > l)
{
v[tr].push_back(segQuery(l, tl - 1, 1, i));
ans[i] -= sum[tl - 1] - sum[l - 1];
tl = l;
}
}
即 dsu on tree \text{dsu on tree} dsu on tree,解决的问题类型如下:
先将所有询问挂在对应的子树根结点 x x x 上,考虑先遍历子结点 y y y 的子树处理其询问,除了最后一个子树外,每遍历完一棵子树就要清除它的影响,而最后一棵子树的信息则可以继承给 x x x。
我们令最后一棵子树为以重儿子为根的子树,由重链剖分的性质,从根到某结点的路径上的轻边个数为 O ( log n ) \mathcal O(\log n) O(logn),因此每个点被清除的次数为 O ( log n ) \mathcal O(\log n) O(logn)。
设计算单点贡献的时间复杂度为 O ( k ) \mathcal O(k) O(k),总时间复杂度 O ( k n log n ) \mathcal O(kn \log n) O(knlogn)。
预处理同重链剖分,具体算法流程如下:
inline void addSubtree(int x)
{
for (int i = pos[x], im = pos[x] + sze[x] - 1; i <= im; ++i)
addCol(col[idx[i]]);
}
inline void decSubtree(int x)
{
for (int i = pos[x], im = pos[x] + sze[x] - 1; i <= im; ++i)
decCol(col[idx[i]]);
}
inline void dfsTraverse(int x)
{
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (y == fa[x] || y == son[x])
continue ;
dfsTraverse(y);
decSubtree(y);
}
if (son[x])
dfsTraverse(son[x]);
addCol(col[x]);
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (y == fa[x] || y == son[x])
continue ;
addSubtree(y);
}
for (int i = 0, im = ask[x].size(); i < im; ++i)
{
pair<int, int> y = ask[x][i];
ans[y.second] = sum[y.first];
}
}
inline bool cmp(const int &x, const int &y)
{
return dfn[x] < dfn[y];
}
inline bool isSubtree(int x, int y)
{
return dfn[y] >= dfn[x] && dfn[y] <= dfn[x] + sze[x] - 1;
}
inline void auxTree()
{
top = 0;
std::sort(vir + 1, vir + m + 1, cmp);
for (int i = 1; i <= m; ++i)
key[vir[i]] = true;
for (int i = 1, im = m; i < im; ++i)
vir[++m] = queryLCA(vir[i], vir[i + 1]);
std::sort(vir + 1, vir + m + 1, cmp);
m = std::unique(vir + 1, vir + m + 1) - vir - 1;
for (int i = 1; i <= m; ++i)
{
while (top && !isSubtree(stk[top], vir[i]))
--top;
if (top)
par[vir[i]] = stk[top];
stk[++top] = vir[i];
}
}