📌  相关文章
📜  使用Binary Lifting在N个数字的前缀和中大于或等于X的第一个元素(1)

📅  最后修改于: 2023-12-03 15:22:18.055000             🧑  作者: Mango

使用Binary Lifting在N个数字的前缀和中大于或等于X的第一个元素

在解决一些问题时,我们需要在一些数据结构中搜索大于或等于某个给定值的第一个元素。在这种情况下,可以使用二进制折半搜索(Binary Search),但是如果我们需要在多个前缀和中进行搜索呢?这时候就可以使用Binary Lifting来解决了。

Binary Lifting

Binary Lifting是一种用于预处理倍增的技术。它通过预处理一些信息,然后利用这些信息在O(log n)的时间内回答一些询问。Binary Lifting通常用于在树上进行搜索。

实现

首先,我们需要预处理每个节点的$2^i$祖先,其中$i$是一个非负整数。为了计算这些祖先,我们可以使用以下代码:

int parent[MAXN][LOGN];
void binaryLifting(int u, int p) {
    parent[u][0] = p;
    for (int i = 1; i < LOGN; ++i) {
        parent[u][i] = parent[parent[u][i-1]][i-1];
    }
    for (int v : adj[u]) {
        if (v != p) {
            binaryLifting(v, u);
        }
    }
}

其中,adj[u]是节点$u$的所有子节点。binaryLifting(u, p)将计算节点$u$的$2^i$祖先,并将节点$p$作为节点$u$的祖先的子节点,其中$i$是它们之间的距离。

例如,节点$u$的$2^4$祖先可以通过以下方式计算:

int fourParent = parent[u][4];
int twoParent = parent[fourParent][2];
int oneParent = parent[twoParent][1];
int zeroParent = parent[oneParent][0];

这种方式被称为“折半”(Binary),它大大降低了计算复杂度。

应用

有了这个预处理,我们可以回答一些问题,例如:

  • 节点$u$的深度是多少?
  • $u$和$v$之间的路径长度是多少?
  • $u$和$v$之间的最近公共祖先是什么?

这些问题的答案都可以在O(log n)的时间内回答。

多个前缀和中大于或等于X的第一个元素

使用Binary Lifting,我们可以在多个前缀和中搜索大于或等于某个给定值的第一个元素。

假设我们有$n$个数字$a_1,a_2,\cdots,a_n$,并且我们需要计算$[1,i]$的前缀和。为了计算任意$[l,r]$的子段和,我们可以使用以下的代码:

int sum = prefixSum[r] - prefixSum[l-1];

假设我们需要找到多个前缀和中大于或等于某个给定值$X$的第一个元素,我们可以对所有前缀和建立一棵线段树,然后使用Binary Lifting即可。

具体来说,我们可以在每个节点上存储其左右子节点中大于或等于$X$的第一个元素。对于每个查询,我们可以使用Binary Lifting在树上查找,直到找到最深的节点,其左子节点的值大于或等于$X$,然后返回该节点对应的位置即可。

以下是一个简单的例子,其中我们需要在三个数组的前缀和中搜索大于或等于$X$的第一个元素:

const int N = 100005;
int n, a[N], b[N], c[N], prefixSumA[N], prefixSumB[N], prefixSumC[N];

// Build segment tree
struct Node {
    int sum, leftGE, rightGE;
};
Node tree[4*N];

void build(int v, int tl, int tr) {
    if (tl == tr) {
        tree[v].sum = prefixSumA[tl];
        tree[v].leftGE = (prefixSumB[tl] >= X) ? tl : -1;
        tree[v].rightGE = (prefixSumC[tl] >= X) ? tl : -1;
        return;
    }
    int tm = (tl + tr) / 2;
    build(2*v, tl, tm);
    build(2*v+1, tm+1, tr);
    tree[v].sum = tree[2*v].sum + tree[2*v+1].sum;
    tree[v].leftGE = (tree[2*v].leftGE != -1) ? tree[2*v].leftGE : tree[2*v+1].leftGE;
    tree[v].rightGE = (tree[2*v+1].rightGE != -1) ? tree[2*v+1].rightGE : tree[2*v].rightGE;
}

// Query segment tree
int query(int v, int tl, int tr, int l, int r) {
    if (l > r) {
        return -1;
    }
    if (l == tl && r == tr) {
        if (tree[v].leftGE != -1 && prefixSumB[tree[v].leftGE] >= X) {
            return tree[v].leftGE;
        }
        if (tree[v].rightGE != -1 && prefixSumC[tree[v].rightGE] >= X) {
            return tree[v].rightGE;
        }
        return -1;
    }
    int tm = (tl + tr) / 2;
    int leftResult = query(2*v, tl, tm, l, min(r, tm));
    int rightResult = query(2*v+1, tm+1, tr, max(l, tm+1), r);
    if (leftResult != -1) {
        return leftResult;
    }
    if (rightResult != -1) {
        return rightResult;
    }
    return -1;
}

// Binary Lifting
int parent[N][LOGN];
void binaryLifting(int u, int p) {
    parent[u][0] = p;
    for (int i = 1; i < LOGN; ++i) {
        parent[u][i] = parent[parent[u][i-1]][i-1];
    }
    for (int v : adj[u]) {
        if (v != p) {
            binaryLifting(v, u);
        }
    }
}
int search(int root, int x) {
    int node = root;
    for (int i = LOGN-1; i >= 0; --i) {
        int leftChild = parent[node][i] << 1;
        int rightChild = (parent[node][i] << 1) + 1;
        if (leftChild <= 2*n) {
            if (tree[leftChild].leftGE != -1 && prefixSumB[tree[leftChild].leftGE] >= x) {
                node = leftChild;
            } else if (tree[rightChild].leftGE != -1 && prefixSumB[tree[rightChild].leftGE] >= x) {
                node = rightChild;
            }
        } else {
            if (prefixSumB[node-2*n] >= x) {
                return node - 2*n;
            }
        }
    }
    return -1;
}

// Example usage
int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        prefixSumA[i] = prefixSumA[i-1] + a[i];
    }
    for (int i = 1; i <= n; ++i) {
        cin >> b[i];
        prefixSumB[i] = prefixSumB[i-1] + b[i];
    }
    for (int i = 1; i <= n; ++i) {
        cin >> c[i];
        prefixSumC[i] = prefixSumC[i-1] + c[i];
    }
    build(1, 1, n);
    binaryLifting(1, 0);
    int q;
    cin >> q;
    while (q--) {
        int x;
        cin >> x;
        int root = 1;
        if (tree[root].sum >= X) {
            int result = search(root, x);
            if (result != -1) {
                cout << result << endl;
                continue;
            }
        }
        cout << "-1" << endl;
    }
    return 0;
}

上面的代码运行时间为O(q log n),其中q是查询次数。