倍增小結 ST 與 LCA

倍增

倍增我是真滴不會

倍增法(英語:binary lifting),顧名思義就是翻倍。
可以使線性的處理轉化爲對數級的處理,大大地優化時間複雜度。
(ps:上次學倍增LCA,沒學會,老老實實爲了嚴格次小生成樹滾回來從新學)node

RMQ_QWQ

ST表ios

\(n~log(n)~\)的預處理與\(O~(1)\)的查詢git

  • \(f_{i,j}\)表示區間\([i,i+2^j - 1]\)的最大值
  • 一開始\(f_{i,0}=a_i\) (\(2^0 -1 = 0\) \(f_{i,0}\)的區間爲\([i,i]\))
  • 轉移方程 ;

    \[f_{i,~j} = max(f_{i,~j-1}, f_{i+2^{j-1},~~j-1}) \]

OI Wike的圖
感性理解一下
對於每次詢問\([l,~r]\)算法

  • \(r = l + 2^x- 1\)
  • \(x = log_2~ (r - l + 1)\)
  • \(ans = max(f_{l,~l + 2^x-1},f_{r - 2^x + 1,~r})\)(此處的表達不是很準確,其實表達應該爲)

    \[f(l,r) = max(f_{l,~l + 2^x-1},f_{r - 2^x + 1,~r}) \]

    將這個\(f(l,r)\)理解爲一個函數,可能就不會有太大的歧義了

Q:這裏爲何不能直接用\(f_{i,~x}\) 呢?數組

A:由於咱們這裏的\(log\)是向下取整的,可能會出現有一塊取不到的部分函數

Q:那有重複的部分怎麼辦吶??優化

A:重複部分對答案的貢獻有影響嗎?spa

Q:貌似莫得影響.net

A:
\(ans = max(f_{l,~x}, f_{r - 2^x + 1,~x})\)指針

完事

注意點¶

  • 輸入輸出數據通常不少,建議開啓輸入輸出優化。

  • 每次用 std::log 從新計算 log 函數值並不值得,建議進行以下的預處理:

\[Log_2 1 = 0 \]

\[Log_2 x = log_2 \frac{x}{2} + 1 \]

第二個式子是這樣推導出來的

\[log_2~x = log_2~2\times\frac{x}{2}\\ ~~~~~~~~~~~~~~~= log_2 + log_2\frac{x}{2}\\ ~~~~~~~~~=1+log_2\frac{x}{2} \]

code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#define ll long long
using namespace std;
const int logn = 22;
const int N = 2000001;
int read() {
  int s = 0, f = 0;
  char ch = getchar();
  while (!isdigit(ch))
    f |= (ch == '-'), ch = getchar();
  while (isdigit(ch))
    s = s * 10 + (ch ^ 48), ch = getchar();
  return f ? -s : s;
}
int Log[N];
int n ,m;
void pre() {
  Log[1] = 0; Log[2] = 1;
  for (int i = 3; i <= n; ++i)
    Log[i] = Log[i / 2] + 1;
}
int f[N][25];
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++i) f[i][0] = read();
  pre();
  for (int j = 1; j <= logn; ++j)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)
      f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
  for (int i = 1; i <= m; i++) {
    int l = read(), r = read();
    int log_x = Log[r - l + 1];
    printf("%d\n", max(f[l][log_x], f[r - (1 << log_x) + 1][log_x]));
  }
  system("pause");
  return 0;
}

倍增求LCA

例題

  • 核心的思想就是每次找祖先的時候多跳幾個以保證時間複雜度的優秀
    先說樸素的算法

能夠每次找深度比較大的那個點,讓它向上跳。顯然在樹上,這兩個點最後必定會相遇,相遇的位置就是想要求的 LCA。 或者先向上調整深度較大的點,令他們深度相同,而後再共同向上跳轉,最後也必定會相遇.(摘自OI Wiki)

精簡版本:

就是從深度深的向上跳,到達同一深度後一塊兒向上跳(學過樹剖的都知道吧,艹估計沒人跟我同樣先學的樹剖,而後回來學倍增)

倍增就很優秀了

  • 本質是樸素算法的改進算法。經過預處理\(f\)數組,能夠使指針快速的移動\(f_{x,i}\) 表示點\(x\)的第\(2^i\)個祖先。這個過程須要經過\(dfs\) 預處理出來。

dfs

void dfs(int x, int fa) {
 dep[x] = dep[fa] + 1;
 f[x][0] = fa;
 for (int i = 1; (1 << i) <= dep[x]; i++)
   f[x][i] = f[f[x][i - 1]][i - 1];
 for (int i = head[x]; i; i = e[i].next) {
   int to = e[i].to;
   if (to != fa)
     dfs(to, x);
 }
}

這種是較爲樸素倍增的求法,沒有用\(log\)優化

加入log優化的(但好像沒太大的常數優化)

Lo數組仍是原來的求法

void dfs(int x, int fa) { 
 f[x][0] = fa;
 dep[x] = dep[fa] + 1;
 for (int i = 1; i <=Log[dep[x]]; ++i)
   f[x][i] =f[f[x][i - 1]][i - 1];  
 for (int i = head[x]; i; i = e[i].net)
   if (e[i].to != fa)
     dfs(e[i].to, x);
}

有一說一,我不大喜歡這種優化的方法我最喜歡的仍是樹剖

  • 裸的倍增LCA,這種寫法確實好理解
#include <cstdio>
#include <iostream>

using namespace std;
const int N = 5e5 + 10;
struct tree {
  int from, to, next;
} e[N << 1];
int nume, head[N];
void add_edge(int from, int to) {
  e[++nume].from = from;
  e[nume].to = to;
  e[nume].next = head[from];
  head[from] = nume;
}
int dep[N], f[N][21];
void dfs(int u, int fa) {
  dep[u] = dep[fa] + 1;
  f[u][0] = fa;
  for (int i = 1; (1 << i) <= dep[u]; i++)
    f[u][i] = f[f[u][i - 1]][i - 1];
  for (int i = head[u]; i; i = e[i].next) {
    int to = e[i].to;
    if (to != fa)
      dfs(to, u);
  }
}
int lca(int x, int y) {
  if (dep[x] > dep[y])
    swap(x, y);
  for (int i = 20; i >= 0; i--) {
    if (dep[x] <= dep[y] - (1 << i))
      y = f[y][i];
  }
  if (x == y)
    return x;
  for (int i = 20; i >= 0; i--) {
    if (f[x][i] == f[y][i])
      continue;
    x = f[x][i], y = f[y][i];
  }
  return f[x][0];
}
int main() {
  int n, m, s;
  scanf("%d%d%d", &n, &m, &s);
  for (int i = 1, x, y; i < n; i++) {
    scanf("%d%d", &x, &y);
    add_edge(x, y);
    add_edge(y, x);
  }
  dfs(s, 0);
  for (int i = 1, x, y; i <= m; i++) {
    scanf("%d%d", &x, &y);
    printf("%d\n", lca(x, y));
  }
}
  • \(Log\)的求法
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#define ll long long
using namespace std;
const int N = 5e5 + 10;
int read() {
  int s = 0, f = 0;
  char ch = getchar();
  while (!isdigit(ch))
    f |= (ch == '-'), ch = getchar();
  while (isdigit(ch))
    s = s * 10 + (ch ^ 48), ch = getchar();
  return f ? -s : s;
}
struct Edge {
  int from, to, net;
} e[N << 1];
int head[N], nume;
void add_edge(int from, int to) {
  e[++nume].from = from, e[nume].to = to, e[nume].net = head[from],
  head[from] = nume;
}
int f[N][25], Log[N], dep[N];
void dfs(int x, int fa) {
  f[x][0] = fa, dep[x] = dep[fa] + 1;
  for (int i = 1; i <= Log[dep[x]]; i++)
    f[x][i] = f[f[x][i - 1]][i - 1];
  for (int i = head[x]; i; i = e[i].net) {
    int to = e[i].to;
    if (to == fa)
      continue;
    dfs(to, x);
  }
}
int lca(int x, int y) {
  if (dep[x] < dep[y])
    swap(x, y);
  while (dep[x] > dep[y])
    x = f[x][Log[dep[x] - dep[y]]];
  if (x == y)
    return x;
  for (int i = Log[dep[x]] ; i >= 0; i--) {
    if (f[x][i] != f[y][i])
      x = f[x][i], y = f[y][i];
  }
  return f[x][0];
}
int main() {
  int n = read(), m = read(), s = read();
  for (int i = 1, u, v; i < n; i++) {
    u = read(), v = read();
    add_edge(u, v), add_edge(v, u);
  }
  Log[1] = 0;
  for (int i = 2; i <= n; i++)
    Log[i] = Log[i / 2] + 1;
  dfs(s, 0);
  for (int i = 1; i <= m; i++) {
    int x = read(), y = read();
    printf("%d\n", lca(x, y));
  }
  system("pause");
  return 0;
}

嗷嗷嗷,有沒有人喜歡樹剖呢

#include <cstdio>
#include <iostream>
using namespace std;

const int N = 5e5 + 10;
int head[N], nume;
struct node {
  int from, to, next;
} e[N << 1];

void add_edge(int from, int to) {
  e[++nume].from = from;
  e[nume].to = to;
  e[nume].next = head[from];
  head[from] = nume;
}
int fath[N], siz[N], dep[N], son[N];
void dfs(int x, int fa) {
  siz[x] = 1;
  fath[x] = fa, dep[x] = dep[fa] + 1;
  for (int i = head[x]; i; i = e[i].next) {
    int to = e[i].to;
    if (to == fa)
      continue;
    dfs(to, x), siz[x] += siz[to];
    if (siz[son[x]] < siz[to])
      son[x] = to;
  }
}
int dfn[N], pre[N], top[N], cnt;
void dfs2(int x, int tp) {
  dfn[x] = ++cnt, pre[cnt] = x, top[x] = tp;
  if (son[x])
    dfs2(son[x], tp);
  for (int i = head[x]; i; i = e[i].next) {
    int to = e[i].to;
    if (to == fath[x] || to == son[x])
      continue;
    dfs2(to, to);
  }
}
int lca(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]])
      y = fath[top[y]];
    else
      x = fath[top[x]];
  }
  if (dep[x] < dep[y])
    swap(x, y);
  return y;
}
int n, m, s;
int main() {
  scanf("%d%d%d", &n, &m, &s);
  int u, v;
  for (int i = 1; i < n; i++) {
    scanf("%d%d", &u, &v);
    add_edge(u, v), add_edge(v, u);
  }
  dfs(s, 0), dfs2(s, s);
  for (int i = 1; i <= m; i++) {
    int x, y;
    scanf("%d%d", &x, &y);
    printf("%d\n", lca(x, y));
  }
  return 0;
}
相關文章
相關標籤/搜索