注:学习倍增需要对二进制有一定了解,并且清楚十进制与二进制的关系
倍增是一种预处理的算法,他用来解决形如:一个状态往后走 k k k 步会走到什么状态,那么此时我们解决这个问题的思路是处理出每一个状态走 2 j 2^j 2j 步以后走到的状态,此时我们将 k k k 进行二进制拆分,把他拆成多个二的整数次幂的和 2 a 1 + 2 a 2 + . . . + 2 a m = k 2^{a_1} + 2^{a_2} + ... + 2^{a_m} = k 2a1+2a2+...+2am=k,请注意,这个问题必须要具有累加性与合并性,即这个状态先走 2 a 1 2^{a_1} 2a1 步,再走 2 a 2 2^{a_2} 2a2 步,…,再走 2 a m 2^{a_m} 2am 后走到的状态就是这个状态往后走 k k k 步的状态。
温馨提示:你可以先去看看序列上的倍增
题意简述:
给出一棵 n n n 个节点的树和 m m m 个询问,对于第 i i i 个询问包含两个整数 u i , k i u_i, k_i ui,ki 表示要查询节点 u i u_i ui 的第 k i k_i ki 代祖先的编号,如果不存在输出 − 1 -1 −1
数据范围:
1 ≤ n , m ≤ 2 × 1 0 5 1 \leq n, m \leq 2 \times 10^5 1≤n,m≤2×105
在最开头说的还记得吗?我们首先来观察这道题有没有累加性,很显然是有的,比如
k
i
=
2
k_i = 2
ki=2,那么点
u
i
u_i
ui 的第
2
2
2 代祖先即
u
i
u_i
ui 的第
1
1
1 代祖先的第
1
1
1 代祖先,比较容易看出来这个问题是具有累加性与合并性的,所以我们考虑把
k
k
k 拆分为二的整数次幂,这个东西直接看
k
k
k 的二进制就行了。那么此时我们的问题就是对于任意节点
u
u
u 我们怎么知道他的
2
k
2^k
2k 代祖先呢(
k
k
k 为任意整数)。很显然我们可以使用一个类似动态规划的思想,即把问题:
u
u
u 的
2
k
2^k
2k 代祖先划分成子问题来合并,得到当前问题的答案。前面我们说了,这个问题具有合并性和累加性,所以我们考虑利用累加性把第
k
k
k 代拆分成多个我们可以算出来的东西,进行合并得到当前的问题。很显然
u
u
u 的
2
k
2^k
2k 代祖先就等于是
u
u
u 的
2
k
−
1
2^{k-1}
2k−1 代祖先的
2
k
−
1
2^{k-1}
2k−1 代祖先(
2
k
−
1
+
2
k
−
1
=
2
k
2^{k-1} + 2^{k-1} = 2^k
2k−1+2k−1=2k)。所以可以得到转移式子:
d
p
[
u
]
[
k
]
=
d
p
[
d
p
[
u
]
[
k
−
1
]
]
[
k
−
1
]
dp[u][k] = dp[dp[u][k-1]][k-1]
dp[u][k]=dp[dp[u][k−1]][k−1]
注:这里的
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示点
i
i
i 的第
2
j
2^j
2j 代祖先的编号。
接着我们就开始拆二进制啦,对于
k
k
k 的第
i
i
i,如果他是
1
1
1 那么我们就往
k
k
k 的拆分数组中加入
2
i
2^{i}
2i,因为
k
k
k 可以表示为他二进制中的
1
1
1 上的位置的对应权值之和。
至此,我们这道题就解决了
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, m, f[N][25];
int main() {
cin >> n;
for (int i = 1, u, v; i < n; i ++ ) {
cin >> u >> v;
f[v][0] = u;
}
for (int j = 1; j <= 20; j ++ )
for (int i = 1; i <= n; i ++ )
f[i][j] = f[f[i][j - 1]][j - 1];
cin >> m;
for (int i = 0, u, v; i < m; i ++ ) {
cin >> u >> v;
int c = 0;
while (v) {
if (v & 1)
u = f[u][c];
++c, v >>= 1;
}
if (u) cout << u << '\n';
else cout << -1 << '\n';
}
return 0;
}
luogu P3865
题意简述:
给出一个长度为 n n n 的数组,有 m m m 个询问,每次询问包含 l i , r i l_i, r_i li,ri,让你求出第 l i l_i li 个数到第 r i r_i ri 个数的最大值
数据范围:
1 ≤ n , m ≤ 2 × 1 0 5 1 \leq n, m \leq 2 \times 10^5 1≤n,m≤2×105
1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109
对于这种序列上的区间查询问题,我们一般的想法是对于每一个位置
i
i
i 算出每一个区间
[
i
,
i
+
2
k
−
1
]
[i, i + 2^k - 1]
[i,i+2k−1] 的最大值,所以我们可以用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示区间
[
i
,
i
+
2
j
−
1
]
[i, i + 2^j - 1]
[i,i+2j−1] 的最大值。
对于
d
p
dp
dp 的初值就是最简的子问题:长度为
1
1
1 的区间,也就是长度为
2
0
2^0
20 的区间,然而长度为
1
1
1 的区间的最大值显然就是那个数,所以我们可以得到初值:
dp[i][0] = a[i]
接着我们考虑怎么用合理复杂度推出
d
p
dp
dp 的值。我们考虑转移。
首先我们发现:
[
i
,
i
+
2
j
−
1
]
[i, i + 2^j - 1]
[i,i+2j−1] 这个区间可以划分为两个区间:
[
i
,
i
+
2
j
−
1
−
1
]
[i, i + 2^{j - 1} - 1]
[i,i+2j−1−1] 和
[
i
+
2
j
−
1
,
i
+
2
j
]
[i + 2^{j - 1}, i + 2^j]
[i+2j−1,i+2j]
因为 2 j − 1 + 2 j − 1 = 2 j 2^{j - 1} + 2^{j - 1} = 2^j 2j−1+2j−1=2j,前半段的长度 + 后半段的长度就等于目标区间的长度
因为最大值具有合并性,并且区间具有累加性,所以:
[
i
,
i
+
2
j
−
1
]
[i, i + 2^j - 1]
[i,i+2j−1] 的最大值就等于
m
a
x
(
max(
max(
[
i
,
i
+
2
j
−
1
−
1
]
[i, i + 2^{j - 1} - 1]
[i,i+2j−1−1] 的最大值,
[
i
+
2
j
−
1
,
i
+
2
j
]
[i + 2^{j - 1}, i + 2^j]
[i+2j−1,i+2j] 的最大值
)
)
)
问题是我们怎么得到划分出的这两个区间的最大值呢?用
d
p
dp
dp 值表示就可以了。
第一个区间
[
i
,
i
+
2
j
−
1
]
[i, i + 2^j - 1]
[i,i+2j−1]:
d
p
[
i
]
[
j
−
1
]
dp[i][j - 1]
dp[i][j−1]
第二个区间
[
i
+
2
j
−
1
,
i
+
2
j
]
[i + 2^{j - 1}, i + 2^j]
[i+2j−1,i+2j]:
d
p
[
i
+
2
j
−
1
]
[
j
−
1
]
dp[i + 2^{j - 1}][j - 1]
dp[i+2j−1][j−1]
所以:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
+
2
j
−
1
]
[
j
−
1
]
)
dp[i][j] = max(dp[i][j - 1], dp[i + 2^{j - 1}][j - 1])
dp[i][j]=max(dp[i][j−1],dp[i+2j−1][j−1])
得到
c
o
d
e
code
code:
for (int j = 1; j <= 20; j ++ )
for (int i = 1; i + (1 << j) - 1 <= n; i ++ )
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j)][j - 1]);
接下来就是怎么用他来算了,首先如果沿用前面的想法就是对于
l
,
r
l, r
l,r,把
r
−
l
+
1
r - l + 1
r−l+1 拆成二进制来做,但是如果要求查询
O
(
1
)
O(1)
O(1) 呢?看似好像沿用刚才的方法是做不到的,那么我们换一种方式,我们不要求正好组成
r
−
l
+
1
r - l + 1
r−l+1,我们只要选出一些区间满足他们覆盖了
[
l
,
r
]
[l, r]
[l,r] 并且没有多覆盖其他的显然也可以(这个可以好好想一想),所以,方便起见,我们直接从左边选一个最大的长度为二的整数次幂并且没有超过
r
r
r 的区间,然后再从右边选一个最大的长度为二的整数次幂并且没有超过
l
l
l 的区间即可,如果你不清楚,可以看一下下面的图片:
这里的
k
k
k 表示满足
2
k
≤
r
−
l
+
1
2^k \leq r - l + 1
2k≤r−l+1 的最大整数,我们可以对于每一个
x
x
x 都预处理出他的
k
k
k 这样就实现了真正的
O
(
1
)
O(1)
O(1)
所以可以得到
c
o
d
e
code
code:
int query(int l, int r) {
int k = bin[r - l + 1]; //bin[x]: 表示x对应的k
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
这里给出算 b i n bin bin 的代码:
for (int i = 2; i <= N; i ++ )
bin[i] = bin[i >> 1] + 1; //其实 bin[x] 就表示 (int)log2(x)
Code:
#include <bits/stdc++.h>
#define pb push_back
#define ll long long
using namespace std;
const int N = 2e5 + 5;
int n, m, ST[N][30], a[N], bin[N];
int main() {
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 2; i < N; i ++ )
bin[i] = bin[i >> 1] + 1;
for (int i = 1; i <= n; i ++ )
cin >> a[i], ST[i][0] = a[i];
for (int j = 1; j <= 20; j ++ )
for (int i = 1; i + (1 << j) - 1 <= n; i ++ )
ST[i][j] = max(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
while (m -- ) {
int l, r; cin >> l >> r;
int k = bin[r - l + 1];
cout << max(ST[l][k], ST[r - (1 << k) + 1][k]) << '\n';
}
return 0;
}
luogu P3379
题意简述:
给出一个 n n n 个节点的树和 m m m 个询问,每次询问包含 u i , v i u_i, v_i ui,vi 求他们的最近共先祖。
我们举个例子:
我们可以首先将
u
u
u 和
v
v
v 跳到同一层,然后我们再进行处理,我们怎么实现呢?显然我们就需要将
u
u
u 往上跳
d
e
p
u
−
d
e
p
v
dep_u - dep_v
depu−depv 次,这样就可以使此时的
d
e
p
u
=
d
e
p
v
dep_u = dep_v
depu=depv 了,这个东西可以用例题
1
1
1 的办法解决
记着我们就来做
L
C
A
LCA
LCA 了
此时
d
e
p
u
=
d
e
p
v
dep_u = dep_v
depu=depv 所以
L
C
A
LCA
LCA 距离
u
,
v
u, v
u,v 的距离是相同的所以我们就尝试跳正确的步数调到
L
C
A
LCA
LCA,设这个正确步数为
s
s
s,那么对于我跳
s
′
(
s
′
>
s
)
s'(s' > s)
s′(s′>s) 步,显然他们都是和
L
C
A
LCA
LCA 一样,都是跳到了一个节点,所以对于一个步数,我并不能确定只要我跳这个步数如果是一个节点就一定是
L
C
A
LCA
LCA,此时发现:
u
,
v
u, v
u,v 分别跳
k
k
k 步,就是
L
C
A
LCA
LCA 下面的点(
k
k
k 是满足
u
,
v
u, v
u,v 分别跳
k
k
k 步不在同一个点的最大整数),所以我们直接二进制拆分
k
k
k 即可,也就是我们枚举从大到小
i
i
i,如果
u
u
u 跳
2
i
2^i
2i 步和
v
v
v 跳
2
i
2^i
2i 步不是一个点,那么就跳,这样不停的跳,最后就是
L
C
A
LCA
LCA 下面的点了,在往上跳一步就可以得到
L
C
A
LCA
LCA 了。
Code(以前的码风):
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dp[N][25],n,m;
int dep[N];vector<int>E[N];
void dp_init(){
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
dp[i][j]=dp[dp[i][j-1]][j-1];
}
void dfs(int u){
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
dep[v]=dep[u]+1;
dfs(v);
}
}
int main() {
cin>>n;dep[1]=1;
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);E[u].pb(v);dp[v][0]=u;
}dp_init(),dfs(1);cin>>m;
for(int t=0,u,v,c;t<m;t++){
cin>>u>>v;
if(dep[u]<dep[v])swap(u,v);
c=dep[u]-dep[v];
for(int i=0;c;c/=2,i++)if(c&1)
u=dp[u][i];
if(u==v){
cout<<u<<'\n';
continue;
}
for(int i=20;i>=0;i--)
if(dp[u][i]!=dp[v][i])
u=dp[u][i],v=dp[v][i];
cout<<dp[u][0]<<'\n';
}
return 0;
}