線性倍增(RMQ)
空間換時間
設狀態 表示區間 的最小值。
如下圖,易得 。
注意邊界爲 , 爲原數組。
這樣,若我們要查詢區間 的最小值,所需時間只需 。
而 ,故 的上界爲 ,該數組佔用的空間爲 (有額外開銷),這即是傳統意義上的“空間換時間”。
如何求值
給定區間 ,用上文的方法我們可以求出區間 與 的最小值。
那麼要求 的最小值,只需讓 與 的併爲 即可。
列方程
解得
(注意這裏的 不是整數)取整時,爲了保險,一般取 。
複雜度
查詢區間最值,如上文所說,單次查詢可以在 的時間內出解。
空間複雜度爲
代碼實現
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int N,Q,a[200001],l,r;
namespace RMQ{
int minn[200001][18],maxn[200001][18];
void build_ST(int *a,int n){
for(int i=1;i<=n;++i)minn[i][0]=maxn[i][0]=a[i];
int lg=(int)log2(n);
for(int j=1;j<=lg;++j)for(int i=1;i+(1<<j)-1<=n;++i){
minn[i][j]=min(minn[i][j-1],minn[i+(1<<j-1)][j-1]);
maxn[i][j]=max(maxn[i][j-1],maxn[i+(1<<j-1)][j-1]);
}
}
int query_min(int l,int r){
int lg=(int)log2(r-l+1);
return min(minn[l][lg],minn[r-(1<<lg)+1][lg]);
}
int query_max(int l,int r){
int lg=(int)log2(r-l+1)-1;
return max(maxn[l][lg],maxn[r-(1<<lg)+1][lg]);
}
}
int main(){
scanf("%d%d",&N,&Q);
for(int i=1;i<=N;++i)scanf("%d",a+i);
RMQ::build_ST(a,N);
for(int i=1;i<=Q;++i){
scanf("%d%d",&l,&r);
printf("Minimum of interval [%d,%d]: %d\n",l,r,RMQ::query_min(l,r));
printf("Maximum of interval [%d,%d]: %d\n",l,r,RMQ::query_max(l,r));
}
}
樹上倍增
樹上倍增的應用非常廣泛,可以在靜態樹中維護各種信息,並可以在 時間內查詢。
基本思想
實質上與RMQ的查詢非常類似,這裏以查詢兩點的LCA舉例。
若用 表示 的第 個祖先,那麼易得遞推式
在dfs時預處理即可。
進軍LCA
假設對於點對 , ,它們的LCA爲 。
若 與 深度相同,考慮 的祖先的性質。由於 ,那麼顯然 的所有祖先都是 與 的公共祖先,反之亦然。因此若 ,那麼 要麼等於 要麼等於 的祖先。故這種情況下,可以考慮枚舉所有 ,若 ,則將 與 更新爲 和 。假設 ,那麼對於 , ,故要從高位(即 , 爲樹高)往低位枚舉 ,每次找到合法的 就更新 和 。
對於深度不同的情況,其實非常簡單,前面假設了 ,又因爲它們深度不同,那麼 ,這時若將 更改爲 的第 個祖先, 顯然不變,於是將 拆爲二進制,用 數組將 的深度置爲與 相同。
擴展:樹上最短路徑最大/最小點權/邊權
模仿求LCA,設 爲 到 路徑上的最小邊權,那麼有狀態轉移方程:
邊界: 的值爲 到 的邊權。
其餘均可同理實現。
代碼實現
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
const int SIZE=500000,LOG2_SIZE=20;
struct tree{
vector<int> point[SIZE+1];
int fa[SIZE+1][LOG2_SIZE],dep[SIZE+1],log_n;
void dfs_dp(int u){
for(int i=1;i<=log_n;++i)
if(fa[u][i-1])
fa[u][i]=fa[fa[u][i-1]][i-1];
else
break;
for(int v:point[u])
if(v!=fa[u][0]){
fa[v][0]=u;
dep[v]=dep[u]+1;
dfs_dp(v);
}
}
void jump(int &v,int d){
int log_d=(int)log2(d);
for(int i=0;i<=log_d;++i)
if(d&(1<<i))
v=fa[v][i];
}
int query(int u,int v){
if(dep[u]>dep[v])
swap(u,v);
jump(v,dep[v]-dep[u]);
if(u==v)
return u;
for(int i=log_n;~i;--i)
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
return fa[u][0];
}
};
tree T;
int N,Q,R,u,v;
int main(){
scanf("%d%d%d",&N,&Q,&R);
for(int i=1;i<N;++i){
scanf("%d%d",&u,&v);
T.point[u].push_back(v);
T.point[v].push_back(u);
}
T.log_n=(int)log2(N);
T.dfs_dp(R);
for(int i=1;i<=Q;++i){
scanf("%d%d",&u,&v);
printf("The lowest common ancestor of %d and %d is: %d\n",T.query(u,v));
}
}