📌  相关文章
📜  子数组中小于或等于数字的元素数:MO 的算法

📅  最后修改于: 2021-09-07 03:28:42             🧑  作者: Mango

给定一个大小为N的数组 arr形式为 L、R 和 X 的 Q查询,任务是打印由 L 到 R 表示的子数组中小于或等于 X 的元素数。

先决条件: MO 算法、Sqrt 分解

例子:

Input: 
arr[] = {2, 3, 4, 5}
Q = {{0, 3, 5}, {0, 2, 2}}
Output:
 4
 1
Explanation:
Number of elements less than or equal to 5
in arr[0..3] is 4 (all elements)

Number of elements less than or equal to 2 
in arr[0..2] is 1 (only 2)

方法:
MO 算法的思想是对所有查询进行预处理,以便一个查询的结果可以在下一个查询中使用。
arr[0…n-1]为输入数组, Q[0..m-1]为查询数组。

  1. 对所有查询进行排序,将 L 值从0 到 √n – 1的查询放在一起,然后将所有查询从√n 到 2×√n – 1 ,依此类推。块内的所有查询都按 R 值的升序排序。
  2. 以每个查询都使用在前一个查询中计算出的结果的方式一个一个地处理所有查询。
  3. 我们将维护频率数组,该数组将计算 arr[i] 出现在 [L, R] 范围内的频率。
  4. 例如: arr[]=[3, 4, 6, 2, 7, 1], L=0, R=4 and X=5

// C++ program to answer queries to 
// count number of elements smaller 
// than or equal to x.
#include 
using namespace std;
  
#define MAX 100001
#define SQRSIZE 400
  
// Variable to represent block size.
// This is made global so compare()
// of sort can use it.
int query_blk_sz;
  
// Structure to represent a
// query range
struct Query {
  
    int L;
    int R;
    int x;
};
  
// Frequency array
// to keep count of elements
int frequency[MAX];
  
// Array which contains the frequency
// of a particular block
int blocks[SQRSIZE];
  
// Block size
int blk_sz;
  
// Function used to sort all queries 
// so that all queries of the same
// block are arranged together and 
// within a block, queries are sorted 
// in increasing order of R values.
bool compare(Query x, Query y)
{
    if (x.L / query_blk_sz != 
        y.L / query_blk_sz)
        return x.L / query_blk_sz < 
               y.L / query_blk_sz;
  
    return x.R < y.R;
}
  
// Function used to get the block
// number of current a[i] i.e ind
int getblocknumber(int ind)
{
    return (ind) / blk_sz;
}
  
// Function to get the answer
// of range [0, k] which uses the
// sqrt decompostion technique
int getans(int k)
{
    int ans = 0;
    int left_blk, right_blk;
    left_blk = 0;
    right_blk = getblocknumber(k);
  
    // If left block is equal to
    // rigth block then we can traverse
    // that block
    if (left_blk == right_blk) {
        for (int i = 0; i <= k; i++)
            ans += frequency[i];
    }
    else {
        // Traversing first block in 
        // range
        for (int i = 0; i < 
             (left_blk + 1) * blk_sz; i++)
            ans += frequency[i];
  
        // Traversing completely overlapped
        // blocks in range
        for (int i = left_blk + 1; 
             i < right_blk; i++)
            ans += blocks[i];
  
        // Traversing last block in range
        for (int i = right_blk * blk_sz;
             i <= k; i++)
            ans += frequency[i];
    }
    return ans;
}
  
void add(int ind, int a[])
{
    // Increment the frequency of a[ind]
    // in the frequency array
    frequency[a[ind]]++;
  
    // Get the block number of a[ind]
    // to update the result in blocks
    int block_num = getblocknumber(a[ind]);
  
    blocks[block_num]++;
}
void remove(int ind, int a[])
{
    // Decrement the frequency of 
    // a[ind] in the frequency array
    frequency[a[ind]]--;
  
    // Get the block number of a[ind]
    // to update the result in blocks
    int block_num = getblocknumber(a[ind]);
  
    blocks[block_num]--;
}
void queryResults(int a[], int n,
                  Query q[], int m)
{
  
    // Initialize the block size
    // for queries
    query_blk_sz = sqrt(m);
  
    // Sort all queries so that queries
    // of same blocks are arranged
    // together.
    sort(q, q + m, compare);
  
    // Initialize current L,
    // current R and current result
    int currL = 0, currR = 0;
  
    for (int i = 0; i < m; i++) {
  
        // L and R values of the 
        // current range
  
        int L = q[i].L, R = q[i].R,
                x = q[i].x;
  
        // Add Elements of current
        // range
        while (currR <= R) {
            add(currR, a);
            currR++;
        }
        while (currL > L) {
            add(currL - 1, a);
            currL--;
        }
  
        // Remove element of previous
        // range
        while (currR > R + 1)
  
        {
            remove(currR - 1, a);
            currR--;
        }
        while (currL < L) {
            remove(currL, a);
            currL++;
        }
        printf("query[%d, %d, %d] : %d\n", 
               L, R, x, getans(x));
    }
}
// Driver code
int main()
{
  
    int arr[] = { 2, 0, 3, 1, 4, 2, 5, 11 };
    int N = sizeof(arr) / sizeof(arr[0]);
  
    blk_sz = sqrt(N);
    Query Q[] = { { 0, 2, 2 }, { 0, 3, 5 },
                { 5, 7, 10 } };
  
    int M = sizeof(Q) / sizeof(Q[0]);
      
    // Answer the queries
    queryResults(arr, N, Q, M);
    return 0;
}
输出:
query[0, 2, 2] : 2
query[0, 3, 5] : 4
query[5, 7, 10] : 2

时间复杂度: O(Q × √N)
MO 的算法需要 O(Q × √N) 时间,而 sqrt 分解技术需要 O(Q × √N) 时间来回答 freq[0]+….freq[k] 的总和,因此总时间复杂度为 O( Q × √N + Q × √N) 即 O(Q × √N)。

如果您想与行业专家一起参加直播课程,请参阅Geeks Classes Live