📌  相关文章
📜  MO的算法(查询平方根分解)|设置1(简介)

📅  最后修改于: 2021-04-24 20:06:03             🧑  作者: Mango

让我们考虑以下问题以理解MO的算法。
给我们一个数组和一组查询范围,我们需要找到每个查询范围的总和。

例子:

Input:  arr[]   = {1, 1, 2, 1, 3, 4, 5, 2, 8};
        query[] = [0, 4], [1, 3] [2, 4]
Output: Sum of arr[] elements in range [0, 4] is 8
        Sum of arr[] elements in range [1, 3] is 4  
        Sum of arr[] elements in range [2, 4] is 6

一个幼稚的解决方案是运行一个从L到R的循环,并为每个查询[L,R]计算给定范围内的元素总数。

C++
// Program to compute sum of ranges for different range
// queries.
#include 
using namespace std;
 
// Structure to represent a query range
struct Query
{
    int L, R;
};
 
// Prints sum of all query ranges. m is number of queries
// n is the size of the array.
void printQuerySums(int a[], int n, Query q[], int m)
{
    // One by one compute sum of all queries
    for (int i=0; i


Java
// Java Program to compute sum of ranges for different range
// queries.
import java.util.*;
  
// Class to represent a query range
class Query{
    int L;
    int R;
    Query(int L, int R){
        this.L = L;
        this.R = R;
    }
}
 
class GFG
{
    // Prints sum of all query ranges. m is number of queries
    // n is the size of the array.
    static void printQuerySums(int a[], int n, ArrayList q, int m)
    {
        // One by one compute sum of all queries
        for (int i=0; i q = new ArrayList();
        q.add(new Query(0,4));
        q.add(new Query(1,3));
        q.add(new Query(2,4));
         
        int m = q.size();
        printQuerySums(a, n, q, m);
    }
}
 
// This code is contributed by shivanisinghss2110


Python3
# Python program to compute sum of ranges for different range queries.
 
# Function that accepts array and list of queries and print sum of each query
def printQuerySum(arr,Q):
     
    for q in Q: # Traverse through each query
        L,R = q # Extract left and right indices
        s = 0
        for i in range(L,R+1): # Compute sum of current query range
            s += arr[i]
             
        print("Sum of",q,"is",s) # Print sum of current query range
 
# Driver script
arr = [1, 1, 2, 1, 3, 4, 5, 2, 8]
Q = [[0, 4], [1, 3], [2, 4]]
printQuerySum(arr,Q)
#This code is contributed by Shivam Singh


C#
// C# program to compute sum of ranges for
// different range queries
using System;
using System.Collections;
   
// Class to represent a query range
public class Query
{
    public int L;
    public int R;
     
    public Query(int L, int R)
    {
        this.L = L;
        this.R = R;
    }
}
  
class GFG{
     
// Prints sum of all query ranges. m
//is number of queries n is the size
// of the array.
static void printQuerySums(int []a, int n,
                       ArrayList q, int m)
{
     
    // One by one compute sum of all queries
    for(int i = 0; i < m; i++)
    {
         
        // Left and right boundaries of
        // current range
        int L = ((Query)q[i]).L,
            R = ((Query)q[i]).R;
  
        // Compute sum of current query range
        int sum = 0;
        for(int j = L; j <= R; j++)
            sum += a[j];
             
        // Print sum of current query range
        Console.Write("Sum of [" + L + ", " +
                      R + "] is " + sum + "\n");
    }
}
  
// Driver code
public static void Main(string []argv)
{
    int []a = { 1, 1, 2, 1, 3, 4, 5, 2, 8 };
    int n = a.Length;
     
    ArrayList q = new ArrayList();
    q.Add(new Query(0, 4));
    q.Add(new Query(1, 3));
    q.Add(new Query(2, 4));
      
    int m = q.Count;
     
    printQuerySums(a, n, q, m);
}
}
 
// This code is contributed by pratham76


C++
// Program to compute sum of ranges for different range
// queries
#include 
using namespace std;
 
// Variable to represent block size. This is made global
// so compare() of sort can use it.
int block;
 
// Structure to represent a query range
struct Query
{
    int L, R;
};
 
// 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)
{
    // Different blocks, sort by block.
    if (x.L/block != y.L/block)
        return x.L/block < y.L/block;
 
    // Same block, sort by R value
    return x.R < y.R;
}
 
// Prints sum of all query ranges. m is number of queries
// n is size of array a[].
void queryResults(int a[], int n, Query q[], int m)
{
    // Find block size
    block = (int)sqrt(n);
 
    // Sort all queries so that queries of same blocks
    // are arranged together.
    sort(q, q + m, compare);
 
    // Initialize current L, current R and current sum
    int currL = 0, currR = 0;
    int currSum = 0;
 
    // Traverse through all queries
    for (int i=0; i L)
        {
            currSum += a[currL-1];
            currL--;
        }
        while (currR <= R)
        {
            currSum += a[currR];
            currR++;
        }
 
        // Remove elements of previous range.  For example
        // when previous range is [0, 10] and current range
        // is [3, 8], then a[9] and a[10] are subtracted
        while (currR > R+1)
        {
            currSum -= a[currR-1];
            currR--;
        }
 
        // Print sum of current range
        cout << "Sum of [" << L << ", " << R
             << "] is "  << currSum << endl;
    }
}
 
// Driver program
int main()
{
    int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
    int n = sizeof(a)/sizeof(a[0]);
    Query q[] = {{0, 4}, {1, 3}, {2, 4}};
    int m = sizeof(q)/sizeof(q[0]);
    queryResults(a, n, q, m);
    return 0;
}


Java
// Java Program to compute sum of ranges for
// different range queries
 
import java.util.*;
 
// Class to represent a query range
class Query{
    int L;
    int R;
    Query(int L, int R){
        this.L = L;
        this.R = R;
    }
}
 
class MO{
 
    // Prints sum of all query ranges. m is number of queries
    // n is size of array a[].
    static void queryResults(int a[], int n, ArrayList q, int m){
         
        // Find block size
        int block = (int) Math.sqrt(n);
     
        // Sort all queries so that queries of same blocks
        // are arranged together.
        Collections.sort(q, new Comparator(){
             
            // 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.
            public int compare(Query x, Query y){
 
                // Different blocks, sort by block.
                if (x.L/block != y.L/block)
                    return (x.L < y.L ? -1 : 1);
 
                // Same block, sort by R value
                return (x.R < y.R ? -1 : 1);
            }
        });
 
        // Initialize current L, current R and current sum
        int currL = 0, currR = 0;
        int currSum = 0;
     
        // Traverse through all queries
        for (int i=0; i L)
            {
                currSum += a[currL-1];
                currL--;
            }
            while (currR <= R)
            {
                currSum += a[currR];
                currR++;
            }
 
            // Remove elements of previous range.  For example
            // when previous range is [0, 10] and current range
            // is [3, 8], then a[9] and a[10] are subtracted
            while (currR > R+1)
            {
                currSum -= a[currR-1];
                currR--;
            }
 
            // Print sum of current range
            System.out.println("Sum of [" + L +
                           ", " + R + "] is "  + currSum);
        }
    }
 
    // Driver program
    public static void main(String argv[]){
        ArrayList q = new ArrayList();
        q.add(new Query(0,4));
        q.add(new Query(1,3));
        q.add(new Query(2,4));
 
        int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
        queryResults(a, a.length, q, q.size());
    }
}
// This code is contributed by Ajay


Python3
# Python program to compute sum of ranges for different range queries
 
import math
 
# Function that accepts array and list of queries and print sum of each query
def queryResults(arr,Q):
     
    #Q.sort(): # Sort by L
    #sort all queries so that all queries in the increasing order of R values . 
    Q.sort(key=lambda x: x[1])
     
    # Initialize current L, current R and current sum
    currL,currR,currSum = 0,0,0
     
    # Traverse through all queries
    for i in range(len(Q)):
        L,R = Q[i] # L and R values of current range
         
        # Remove extra elements from previous range
        # if previous range is [0, 3] and current 
        # range is [2, 5], then a[0] and a[1] are subtracted 
        while currLL:
            currSum+=arr[currL-1]
            currL-=1
        while currR<=R:
            currSum+=arr[currR]
            currR+=1
             
        # Remove elements of previous range
        # when previous range is [0, 10] and current range 
        # is [3, 8], then a[9] and a[10] are subtracted 
        while currR>R+1:
            currSum-=arr[currR-1]
            currR-=1
         
        # Print the sum of current range
        print("Sum of",Q[i],"is",currSum)
 
arr = [1, 1, 2, 1, 3, 4, 5, 2, 8]
Q = [[0, 4], [1, 3], [2, 4]]
queryResults(arr,Q)
#This code is contributed by Shivam Singh


输出:

Sum of [0, 4] is 8
Sum of [1, 3] is 4
Sum of [2, 4] is 6

上述解决方案的时间复杂度为O(mn)。
MO的算法的思想是对所有查询进行预处理,以便一个查询的结果可以在下一个查询中使用。以下是步骤。

a [0…n-1]为输入数组, q [0..m-1]为查询数组。

  1. 对所有查询进行排序,将L值从0√n– 1的查询放在一起,然后将所有从√n2 *√n– 1的查询放在一起,依此类推。块中的所有查询均按R值的升序排序。
  2. 以每个查询都使用上一个查询中计算得出的总和的方式来一一处理所有查询。
    • 令“ sum”为上一个查询的总和。
    • 删除上一个查询的其他元素。例如,如果先前的查询为[0,8]而当前的查询为[3,9],则我们从总和中减去a [0],a [1]和a [2]
    • 添加当前查询的新元素。在与上述相同的示例中,我们将a [9]加到sum上。

该算法的优点在于,在第2步中,R的索引变量在整个运行过程中最多变化O(n *√n)次,而L的索引变量最多变化O(m *√n)次(请参见下文) ,在代码之后,以获取详细信息)。所有这些限制都是可能的,因为查询首先以√n大小的块进行排序。

预处理部分需要O(m Log m)时间。

处理所有查询需要O(n *√n) + O(m *√n) = O((m + n)*√n)时间。

以下是上述想法的实现。

C++

// Program to compute sum of ranges for different range
// queries
#include 
using namespace std;
 
// Variable to represent block size. This is made global
// so compare() of sort can use it.
int block;
 
// Structure to represent a query range
struct Query
{
    int L, R;
};
 
// 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)
{
    // Different blocks, sort by block.
    if (x.L/block != y.L/block)
        return x.L/block < y.L/block;
 
    // Same block, sort by R value
    return x.R < y.R;
}
 
// Prints sum of all query ranges. m is number of queries
// n is size of array a[].
void queryResults(int a[], int n, Query q[], int m)
{
    // Find block size
    block = (int)sqrt(n);
 
    // Sort all queries so that queries of same blocks
    // are arranged together.
    sort(q, q + m, compare);
 
    // Initialize current L, current R and current sum
    int currL = 0, currR = 0;
    int currSum = 0;
 
    // Traverse through all queries
    for (int i=0; i L)
        {
            currSum += a[currL-1];
            currL--;
        }
        while (currR <= R)
        {
            currSum += a[currR];
            currR++;
        }
 
        // Remove elements of previous range.  For example
        // when previous range is [0, 10] and current range
        // is [3, 8], then a[9] and a[10] are subtracted
        while (currR > R+1)
        {
            currSum -= a[currR-1];
            currR--;
        }
 
        // Print sum of current range
        cout << "Sum of [" << L << ", " << R
             << "] is "  << currSum << endl;
    }
}
 
// Driver program
int main()
{
    int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
    int n = sizeof(a)/sizeof(a[0]);
    Query q[] = {{0, 4}, {1, 3}, {2, 4}};
    int m = sizeof(q)/sizeof(q[0]);
    queryResults(a, n, q, m);
    return 0;
}

Java

// Java Program to compute sum of ranges for
// different range queries
 
import java.util.*;
 
// Class to represent a query range
class Query{
    int L;
    int R;
    Query(int L, int R){
        this.L = L;
        this.R = R;
    }
}
 
class MO{
 
    // Prints sum of all query ranges. m is number of queries
    // n is size of array a[].
    static void queryResults(int a[], int n, ArrayList q, int m){
         
        // Find block size
        int block = (int) Math.sqrt(n);
     
        // Sort all queries so that queries of same blocks
        // are arranged together.
        Collections.sort(q, new Comparator(){
             
            // 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.
            public int compare(Query x, Query y){
 
                // Different blocks, sort by block.
                if (x.L/block != y.L/block)
                    return (x.L < y.L ? -1 : 1);
 
                // Same block, sort by R value
                return (x.R < y.R ? -1 : 1);
            }
        });
 
        // Initialize current L, current R and current sum
        int currL = 0, currR = 0;
        int currSum = 0;
     
        // Traverse through all queries
        for (int i=0; i L)
            {
                currSum += a[currL-1];
                currL--;
            }
            while (currR <= R)
            {
                currSum += a[currR];
                currR++;
            }
 
            // Remove elements of previous range.  For example
            // when previous range is [0, 10] and current range
            // is [3, 8], then a[9] and a[10] are subtracted
            while (currR > R+1)
            {
                currSum -= a[currR-1];
                currR--;
            }
 
            // Print sum of current range
            System.out.println("Sum of [" + L +
                           ", " + R + "] is "  + currSum);
        }
    }
 
    // Driver program
    public static void main(String argv[]){
        ArrayList q = new ArrayList();
        q.add(new Query(0,4));
        q.add(new Query(1,3));
        q.add(new Query(2,4));
 
        int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
        queryResults(a, a.length, q, q.size());
    }
}
// This code is contributed by Ajay

Python3

# Python program to compute sum of ranges for different range queries
 
import math
 
# Function that accepts array and list of queries and print sum of each query
def queryResults(arr,Q):
     
    #Q.sort(): # Sort by L
    #sort all queries so that all queries in the increasing order of R values . 
    Q.sort(key=lambda x: x[1])
     
    # Initialize current L, current R and current sum
    currL,currR,currSum = 0,0,0
     
    # Traverse through all queries
    for i in range(len(Q)):
        L,R = Q[i] # L and R values of current range
         
        # Remove extra elements from previous range
        # if previous range is [0, 3] and current 
        # range is [2, 5], then a[0] and a[1] are subtracted 
        while currLL:
            currSum+=arr[currL-1]
            currL-=1
        while currR<=R:
            currSum+=arr[currR]
            currR+=1
             
        # Remove elements of previous range
        # when previous range is [0, 10] and current range 
        # is [3, 8], then a[9] and a[10] are subtracted 
        while currR>R+1:
            currSum-=arr[currR-1]
            currR-=1
         
        # Print the sum of current range
        print("Sum of",Q[i],"is",currSum)
 
arr = [1, 1, 2, 1, 3, 4, 5, 2, 8]
Q = [[0, 4], [1, 3], [2, 4]]
queryResults(arr,Q)
#This code is contributed by Shivam Singh

输出:

Sum of [1, 3] is 4
Sum of [0, 4] is 8
Sum of [2, 4] is 6

上面程序的输出不会以与输入相同的顺序打印查询结果,因为查询是经过排序的。该程序可以轻松扩展以保持相同的顺序。

重要观察:

  1. 所有查询都是事先知道的,以便可以对其进行预处理
  2. 对于我们在更新操作中也混入总和查询的问题,它不起作用。
  3. MO的算法只能用于查询问题,在该问题中,可以根据前一个查询的结果来计算查询。再一个这样的例子是最大或最小。

时间复杂度分析:
该函数主要为所有排序查询运行一个for循环。在for循环内,有四个while查询可移动’currL’和’currR’。

移动了多少currR?对于每个块,查询以R的升序排序。因此,对于一个块,currR以升序移动。在最坏的情况下,在每个块开始之前,currR位于最右端,当前块将其移回最左端。这意味着,对于每个块,currR最多移动O(n) 。由于存在O(√n)个块,所以currR的总移动量为O(n *√n)

移动了多少课程?由于所有查询的排序方式都是L个值按块分组,因此当我们从一个查询移到另一个查询时,移动为O( √n)。对于m个查询,currL的总移动量为O(m *√n)
请注意,解决此问题的一种简单高效的解决方案是计算从0到n-1的所有元素的前缀和。令前缀总和存储在数组preSum []中(preSum [i]的值存储arr [0..i]的总和)。一旦构建了preSum [],我们就可以一个一遍地遍历所有查询。对于每个查询[L,R],我们返回preSum [R] – preSum [L]的值。在这里,处理每个查询需要O(1)时间。

本文的想法是通过一个非常简单的示例介绍MO的算法。我们将很快讨论使用MO算法的更有趣的问题。

范围最小查询(平方根分解和稀疏表)