https://ac.nowcoder.com/discuss/395376
https://ac.nowcoder.com/acm/problem/13331
ST表
倍增的最基础应用应该是ST表。ST表本质是动态规划,主要用于解决RMQ(Range Minimum/Maximum Query, 即区间最值查询) 问题,是一种离线算法。
以求区间最大值为例,它的基本思想是:用
f
[
i
]
[
j
]
f[i][j]
f[i][j]维护从从i开始长度为
2
j
2^{j}
2j的区间的最大值,例如
f
[
1
]
[
1
]
{f[1] [1]}
f[1][1]表示的是以1开始长度为2的区间的最大值,
f
[
1
]
[
2
]
{f[1][2]}
f[1][2]表达的是以1开始长度为4的区间的最大值。我们可以看到
j
{j}
j每增加1,区间的长度乘以二,于是两个相邻区间合并就可以得到当前这个长度的区间的最大值了,也就是说
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
−
1
]
,
f
[
i
+
2
j
−
1
]
[
j
−
1
]
)
f[i] [j] = max(f[i] [j-1], f[i+2^{j-1}] [j-1])
f[i][j]=max(f[i][j−1],f[i+2j−1][j−1]),这样
O
(
n
l
o
g
n
)
{O(nlogn)}
O(nlogn)的时间复杂度我们就可以维护出
f
f
f数组。
求区间
[
L
,
R
]
[L,R]
[L,R]的最大值,只要求出两个刚好大于
(
R
−
L
)
/
2
(R-L)/2
(R−L)/2的区间(一个左边界为
L
L
L,一个右边界为
R
R
R)就好了,
伪代码参考:
void RMQ_ST(int n) //预处理ST表,数组中有n个元素
{
for (int i = 1; i <= n; i++)
f[i][0] = a[i];
int m = (int)(log((double)n) / log(2.0));
for (int j = 1; j <= m; j++) //先循环j再循环i,因为必须要把长度为1的区间都算出来了才能求长度为2的
for (int i = 1; i + (1 << j) - 1 <= n; i++)
f[i][j] = min(f[i][j - 1], f[i + (1 << j)][j - 1]);
}
int RMQ_find(int l, int r) //求区间 l 到 r 之间的最值
{
int k = (int)(log(1.0 * (r - l + 1)) / log(2.0)); //我们用两个长度刚好大于(r-l+1)/2的区间拼起来
return min(f[l][k], f[r - (1 << k) + 1][k]);
}
题目描述
有一个树状的城市网络(即
n
n
n 个城市由
n
−
1
n-1
n−1 条道路连接的连通图),首都为 1 号城市,每个城市售卖价值为
a
i
a_i
ai 的珠宝。
你是一个珠宝商,现在安排有
q
q
q 次行程,每次行程为从
u
u
u 号城市前往
v
v
v 号城市(走最短路径),保证
v
v
v 在
u
u
u 前往首都的最短路径上。
在每次行程开始时,你手上有价值为
c
c
c 的珠宝(每次行程可能不同),并且每经过一个城市时(包括
u
u
u 和
v
v
v ),假如那个城市中售卖的珠宝比你现在手上的每一种珠宝都要优秀(价值更高,即严格大于),那么你就会选择购入。
现在你想要对每一次行程,求出会进行多少次购买事件。
输入描述:
第一行,两个正整数
n
,
q
(
2
≤
n
≤
1
0
5
,
1
≤
q
≤
1
0
5
)
n , q (2 ≤ n ≤ 10^5 , 1 ≤ q ≤ 10^5)
n,q(2≤n≤105,1≤q≤105)。
第二行,
n
n
n 个正整数
a
i
(
1
≤
a
i
≤
1
0
5
)
a_i (1 ≤ a_i ≤ 10^5)
ai(1≤ai≤105) 描述每个城市售卖的珠宝的价值。
接下来
n
−
1
n-1
n−1 行,每行描述一条道路
x
,
y
(
1
≤
x
,
y
≤
n
)
x , y (1 ≤ x,y ≤ n)
x,y(1≤x,y≤n),表示有一条连接
x
x
x 和
y
y
y 的道路。
接下来
q
q
q 行,每行描述一次行程
u
,
v
,
c
(
1
≤
u
,
v
≤
n
,
1
≤
c
≤
1
0
5
)
u , v , c (1 ≤ u,v ≤ n , 1 ≤ c ≤ 10^5)
u,v,c(1≤u,v≤n,1≤c≤105)。
输出描述:
对于每次行程输出一行,为所购买次数。
input:
5 4
3 5 1 2 4
1 2
1 3
2 4
3 5
4 2 1
4 2 2
4 2 3
5 1 5
output:
2
1
1
0
树上倍增
本题用的是树上倍增。这个题难了我两天😭,终于看懂了。
题目描述中的黄色的两句是关键,看见了这两句就能知道用树上倍增了。
用
f
[
i
]
[
j
]
{f[i] [j]}
f[i][j] 表示从
i
i
i 往上走,能买到的第
2
j
2^j
2j 个珠宝点是哪个点,显然,如果我们知道每个
f
[
i
]
[
0
]
{f[i] [0]}
f[i][0] 的值,那么
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
{f[i] [j] =f[f[i] [j-1]] [j-1]}
f[i][j]=f[f[i][j−1]][j−1] (
i
{i}
i 往上的第
2
j
−
1
2^{j-1}
2j−1 个点再往上
2
j
−
1
2^{j-1}
2j−1 个点)。求
f
[
i
]
[
0
]
f[i][0]
f[i][0] 用倍增法求解。查询开始时手上有价值
c
c
c 的珠宝怎么办?可以在每次查询的起点加一个权值为
c
c
c 的子节点作为起点。详解看上面给的链接。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5; //因为查询要单独建立一个起点,所以要把q次查询加到总的点数中,1e5+1e5
vector<int> G[maxn]; //存图
int a[maxn]; //每个点的权值a_i
int f[maxn][20]; //f[i][j]表示从i往上走,能买到第2^j个珠宝的点是哪个,2^19=524288>2e5
int to[maxn]; //记录q次查询的终点
int deep[maxn]; //记录每个点的深度,根节点d[1]=0
void dfs(int cur, int fa)
{
int x = fa;
for (int i = 19; i >= 0; i--)
if (f[x][i] && a[f[x][i]] <= a[cur]) //如果x往上跳2^k步这个点存在,且这个点的权值比a[cur]小,说明这个点以下的点都比a[cur]小,所以x就跳到这个点再往上跳
x = f[x][i];
if (a[x] > a[cur]) //判断比a[cur]大的点是x还是x上面那个点
f[cur][0] = x;
else
f[cur][0] = f[x][0];
for (int i = 1; i <= 19; i++)
f[cur][i] = f[f[cur][i - 1]][i - 1]; //倍增求出每个点向上2^i步的点
deep[cur] = deep[fa] + 1; //维护高度,查找是用来判断终点
int sz = G[cur].size();
for (int i = 0; i < sz; i++)
{
int v = G[cur][i];
if (v != fa)
dfs(v, cur); //搜儿子
}
}
int main()
{
#ifdef LOCAL_LIUZHIHAN
freopen("in.in", "r", stdin);
// freopen("out.out", "w", stdout);
#endif
int n, q;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int x, y;
for (int i = 1; i < n; i++)
{
scanf("%d%d", &x, &y);
G[x].push_back(y);
G[y].push_back(x);
}
for (int i = n + 1; i <= n + q; i++)
{
scanf("%d%d%d", &x, &y, &a[i]);
G[i].push_back(x);
G[x].push_back(i); //在u点的下方接一个权值为c的点作为起点
to[i - n] = y; //记录q次查询的终点
}
dfs(1, 0);
for (int i = 1; i <= q; i++)
{
int ans = 0;
x = i + n; //x是查询的起点的编号
for (int k = 19; k >= 0; k--) //从大到小枚举k
if (deep[f[x][k]] >= deep[to[i]]) //如果跳完之后到达的点深度大于等于目标点,表示在u->v的最短路上,那么就跳到这个点继续往上跳
{
ans += (1 << k);
x = f[x][k];
}
printf("%d\n", ans);
}
return 0;
}