Open In App

Counting segment inversions of an Array with updates

Last Updated : 06 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Given an array of small integers arr[] and a 2D array of queries[][], In each query array we have the first element indicating the operation type, and the remaining elements are the arguments.

  • If query[i][0] is ‘1’ then query[i][1] = l and query[i][2] = r and we have to Find the number of inversions in the segment from l to r (inclusive) of the array and print the result.
  • If query[i][0] is ‘2’ then query[i][1] = idx and query[i][2] = v and we have to change the element at index idx of the array to value v.

Examples:

Input: arr[] = {1, 2, 3, 6, 5, 4} , queries={{1, 1, 3}, {1, 2, 5}, {2, 2, 8}, {1, 1, 4}}
Output: 0 1 2
Explanation:

  • In the first query, there are no inversions in segment [1, 3], so the output is 0.
  • In the second query, there is one inversion in segment [2, 5]: (6, 5), so the output is 1.
  • In the third query, the Type of the query is 2 so arr is changed to {1 8 3 6 5 4}.
  • In the final query, there are now two inversions in segment [1, 4]: (8,3), (8, 6), so the output is 2.

Naïve Approach: The basic way to solve the problem is as follows:

For operation Type 1, we can use two nested for loops to check for each pair of elements and count the number of them that form an inversion. Then, for operation Type 2, we simply assign the given value to arr[i].

Time Complexity: O(n2)
Auxiliary Space: O(n)

Efficient Approach: To solve the problem follow the below idea:

We can use a Segment tree to optimize the segment queries, while maintaining a good time complexity in updates.

Steps to solve the problem:

  • Create a Segment Tree for each node and Store two information on each segment node:
    • The number of inversions on the segment.
    • A frequency array, where freq[i] represents number of times i appears on the segment.
  • For single element segments, we have that the number of inversions is 0 and the frequency array is filled with 0s, having 1 only on the index of its value. For bigger segments, we use a merge function.
  • Given that we are merging the left and right nodes into the parent node, we have the following:
    • Each entry of the frequency array of the parent node will be the sum of that entry on each of the child nodes: parent.freq[i] = left.freq[i] + right.freq[i]
    • The number of inversions will be the sum of the inversions on each node plus the number of inversions between the two nodes. We assume we already have the two first numbers, so we only need to calculate the third.
  • In Merge function: for each element in V, there is an inversion whenever they are present in the left node and there are smaller elements present on the right node. This means we can write two nested loops and sum to the inversions counter the product of the frequency of the bigger element on the left times the frequency of the smaller element on the right: this calculation can be optimized using a prefix sum array.
  • Having an algorithm to merge two child nodes of the segment tree on a parent node, we only need to implement the query and the update functions.
    • Query function will be a typical query implementation for segment trees, using a node with number of inversions and all entries in frequency array equal to 0 as a “neutral” operand of the merge function.
    • Update function will be pretty standard as well. When we find the node of segment [idx, idx], we change its frequency array accordingly and update the parent nodes through recursion. We can either set all the frequency array entries to 0 and change freq[v] to 1 or maintain the current state of the array and update just the previous value entry to 0, because we know all other entries are 0 already.
  • With this approach, we first spend O(n) building the segment tree with its initial values and O(logn) for both operations, resulting in a total time complexity of O(n + q*logn), where q is the number of queries.

Below is the implementation of the above idea:

C++




// C++ code for the above approach:
#include <iostream>
#include <vector>
 
using namespace std;
 
typedef long long ll;
 
// OBS: 0-based values
constexpr int MAX_V = 40 + 5;
 
struct item {
 
    // Number of inversions in range
    ll inv_cnt;
 
    // freq[n] => number of times n appears
    // in seg
    int freq[MAX_V];
};
 
struct segtree {
 
    // Empty seg
    const item NEUTRAL{
        .inv_cnt = 0,
        .freq = { 0 },
    };
 
    int n;
    vector<int> arr;
    vector<item> seg;
 
    item merge(item const& left, item const& right)
    {
        item parent;
 
        parent.inv_cnt = left.inv_cnt + right.inv_cnt;
 
        // prev[i] => #el <= i on right
        int prev[MAX_V];
        prev[0] = right.freq[0];
 
        for (int i = 0; i < MAX_V; i++) {
            parent.freq[i] = left.freq[i] + right.freq[i];
 
            if (i != 0) {
                // avoids overflow
                parent.inv_cnt
                    += 1LL * left.freq[i] * prev[i - 1];
                prev[i] = prev[i - 1] + right.freq[i];
            }
        }
 
        return parent;
    }
 
    item build(int p, int l, int r)
    {
        if (l == r) {
            seg[p].inv_cnt = 0;
            seg[p].freq[arr[l]] = 1;
            return seg[p];
        }
        int m = (l + r) / 2;
        return seg[p] = merge(build(2 * p, l, m),
                              build(2 * p + 1, m + 1, r));
    }
 
    item build() { return build(1, 0, n - 1); }
 
    segtree(vector<int>& arr_val)
        : n(arr_val.size())
        , arr(arr_val)
    {
        seg.resize(4 * n);
        build();
    }
 
    item set(int i, int v, int p, int l, int r)
    {
        if (i < l or i > r)
            return seg[p];
        if (l == r) {
            seg[p].freq[arr[i]] = 0;
            seg[p].freq[v] = 1;
            arr[i] = v;
            return seg[p];
        }
        int m = (l + r) / 2;
        return seg[p]
               = merge(set(i, v, 2 * p, l, m),
                       set(i, v, 2 * p + 1, m + 1, r));
    }
 
    item set(int i, int v)
    {
        return set(i, v, 1, 0, n - 1);
    }
 
    item query(int lx, int rx, int p, int l, int r)
    {
        if (lx > r or l > rx)
            return NEUTRAL;
        if (lx <= l and r <= rx)
            return seg[p];
        int m = (l + r) / 2;
        return merge(query(lx, rx, 2 * p, l, m),
                     query(lx, rx, 2 * p + 1, m + 1, r));
    }
 
    ll query(int lx, int rx)
    {
        return query(lx, rx, 1, 0, n - 1).inv_cnt;
    }
};
 
// Driver code
int main()
{
 
    // Example inputs
    vector<int> arr = { 1, 2, 3, 6, 5, 4 };
    vector<vector<int> > queries = {
        { 1, 1, 3 }, { 1, 2, 5 }, { 2, 2, 8 }, { 1, 1, 4 }
    };
 
    segtree st(arr);
 
    for (auto query : queries) {
        int const type = query[0];
        if (type == 1) {
 
            // Making Zero based indexing
            query[1]--;
            query[2]--;
            cout << st.query(query[1], query[2]) << endl;
        }
        else if (type == 2) {
 
            // Making Zero based indexing
            query[1]--;
            st.set(query[1], query[2]);
        }
    }
 
    return 0;
}


Java




import java.util.Arrays;
import java.util.List;
 
class Item {
    long invCount;
    int[] freq = new int[46]; // Frequency array for values from 0 to 45
 
    public Item() {
        invCount = 0;
        Arrays.fill(freq, 0);
    }
}
 
class SegmentTree {
    final Item NEUTRAL = new Item();
    int n;
    int[] arr;
    Item[] seg;
 
    public SegmentTree(int[] arr) {
        this.n = arr.length;
        this.arr = Arrays.copyOf(arr, n);
        this.seg = new Item[4 * n];
        build(1, 0, n - 1);
    }
 
    Item merge(Item left, Item right) {
        Item parent = new Item();
        parent.invCount = left.invCount + right.invCount;
        int[] prev = new int[46];
        prev[0] = right.freq[0];
 
        for (int i = 0; i < 46; i++) {
            parent.freq[i] = left.freq[i] + right.freq[i];
            if (i != 0) {
                parent.invCount += 1L * left.freq[i] * prev[i - 1];
                prev[i] = prev[i - 1] + right.freq[i];
            }
        }
        return parent;
    }
 
    Item build(int p, int l, int r) {
        if (l == r) {
            seg[p] = new Item();
            seg[p].invCount = 0;
            seg[p].freq[arr[l]] = 1;
            return seg[p];
        }
        int m = (l + r) / 2;
        return seg[p] = merge(build(2 * p, l, m), build(2 * p + 1, m + 1, r));
    }
 
    Item build() {
        return build(1, 0, n - 1);
    }
 
    Item set(int i, int v, int p, int l, int r) {
        if (i < l || i > r) return seg[p];
        if (l == r) {
            seg[p].freq[arr[i]] = 0;
            seg[p].freq[v] = 1;
            arr[i] = v;
            return seg[p];
        }
        int m = (l + r) / 2;
        return seg[p] = merge(set(i, v, 2 * p, l, m), set(i, v, 2 * p + 1, m + 1, r));
    }
 
    Item set(int i, int v) {
        return set(i, v, 1, 0, n - 1);
    }
 
    Item query(int lx, int rx, int p, int l, int r) {
        if (lx > r || l > rx) return NEUTRAL;
        if (lx <= l && r <= rx) return seg[p];
        int m = (l + r) / 2;
        return merge(query(lx, rx, 2 * p, l, m), query(lx, rx, 2 * p + 1, m + 1, r));
    }
 
    long query(int lx, int rx) {
        return query(lx, rx, 1, 0, n - 1).invCount;
    }
}
 
public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 6, 5, 4};
        List<int[]> queries = Arrays.asList(
            new int[] {1, 1, 3},
            new int[] {1, 2, 5},
            new int[] {2, 2, 8},
            new int[] {1, 1, 4}
        );
 
        SegmentTree st = new SegmentTree(arr);
 
        for (int[] query : queries) {
            int type = query[0];
            if (type == 1) {
                int l = query[1] - 1;
                int r = query[2] - 1;
                System.out.println(st.query(l, r));
            } else if (type == 2) {
                int i = query[1] - 1;
                st.set(i, query[2]);
            }
        }
    }
}


Python3




class SegTree:
    def __init__(self, arrVal):
        self.NEUTRAL = Item(45)
        self.n = len(arrVal)
        self.arr = arrVal
        # Empty seg
        self.seg = [Item(45) for _ in range(4 * self.n)]
        self.build(1, 0, self.n - 1)
 
    def merge(self, left, right):
        parent = Item(len(left.freq))
        #avoids overflow
        parent.invCnt = left.invCnt + right.invCnt
        prev = [0] * len(left.freq)
        prev[0] = right.freq[0]
 
        for i in range(len(left.freq)):
            parent.freq[i] = left.freq[i] + right.freq[i]
 
            if i != 0:
                parent.invCnt += 1 * left.freq[i] * prev[i - 1]
                prev[i] = prev[i - 1] + right.freq[i]
 
        return parent
 
    def build(self, p, l, r):
        if l == r:
            self.seg[p].invCnt = 0
            self.seg[p].freq[self.arr[l]] = 1
            return self.seg[p]
        m = (l + r) // 2
        self.seg[p] = self.merge(self.build(2 * p, l, m), self.build(2 * p + 1, m + 1, r))
        return self.seg[p]
 
    def build_tree(self):
        return self.build(1, 0, self.n - 1)
 
    def set(self, i, v, p, l, r):
        if i < l or i > r:
            return self.seg[p]
        if l == r:
            self.seg[p].freq[self.arr[i]] = 0
            self.seg[p].freq[v] = 1
            self.arr[i] = v
            return self.seg[p]
        m = (l + r) // 2
        self.seg[p] = self.merge(self.set(i, v, 2 * p, l, m), self.set(i, v, 2 * p + 1, m + 1, r))
        return self.seg[p]
 
    def set_value(self, i, v):
        return self.set(i, v, 1, 0, self.n - 1)
 
    def query(self, lx, rx, p, l, r):
        if lx > r or l > rx:
            return self.NEUTRAL
        if lx <= l and r <= rx:
            return self.seg[p]
        m = (l + r) // 2
        return self.merge(self.query(lx, rx, 2 * p, l, m), self.query(lx, rx, 2 * p + 1, m + 1, r))
 
    def query_range(self, lx, rx):
        return self.query(lx, rx, 1, 0, self.n - 1).invCnt
 
 
class Item:
    def __init__(self, maxV):
        self.invCnt = 0
        self.freq = [0] * maxV
 
 
def main():
    arr = [1, 2, 3, 6, 5, 4]
    queries = [
        [1, 1, 3],
        [1, 2, 5],
        [2, 2, 8],
        [1, 1, 4]
    ]
 
    st = SegTree(arr)
 
    for query in queries:
        type = query[0]
        if type == 1:
            l = query[1] - 1  # Making Zero based indexing
            r = query[2] - 1  # Making Zero based indexing
            print(st.query_range(l, r))
        elif type == 2:
            i = query[1] - 1  # Making Zero based indexing
            v = query[2]
            st.set_value(i, v)
#Driver code
 
if __name__ == "__main__":
    main()


C#




using System;
using System.Collections.Generic;
 
class Item
{
    public long invCount;
    public int[] freq = new int[46]; // Frequency array for values from 0 to 45
 
    public Item()
    {
        invCount = 0;
        // Initialize frequency array
        Array.Fill(freq, 0);
    }
}
 
class SegmentTree
{
    private readonly Item NEUTRAL = new Item();
    private int n;
    private int[] arr;
    private Item[] seg;
 
    public SegmentTree(int[] arr)
    {
        n = arr.Length;
        // Create a copy of the input array
        this.arr = (int[])arr.Clone();
        seg = new Item[4 * n]; // Initialize segment tree array
        Build(1, 0, n - 1); // Build the segment tree
    }
 
    private Item Merge(Item left, Item right)
    {
        // Merge two items to produce a parent item
        var parent = new Item();
        parent.invCount = left.invCount + right.invCount;
        int[] prev = new int[46];
        prev[0] = right.freq[0];
 
        // Iterate through frequency array to calculate inversions
        for (int i = 0; i < 46; i++)
        {
            parent.freq[i] = left.freq[i] + right.freq[i];
            if (i != 0)
            {
                parent.invCount += 1L * left.freq[i] * prev[i - 1];
                prev[i] = prev[i - 1] + right.freq[i];
            }
        }
        return parent;
    }
 
    private Item Build(int p, int l, int r)
    {
        if (l == r)
        {
            // Leaf node initialization
            seg[p] = new Item();
            seg[p].invCount = 0;
            seg[p].freq[arr[l]] = 1;
            return seg[p];
        }
        int m = (l + r) / 2;
        // Recursive call to build left and right subtrees
        return seg[p] = Merge(Build(2 * p, l, m), Build(2 * p + 1, m + 1, r));
    }
 
    private Item Set(int i, int v, int p, int l, int r)
    {
        // Update a value at index i with value v
        if (i < l || i > r) return seg[p];
        if (l == r)
        {
            seg[p].freq[arr[i]] = 0;
            seg[p].freq[v] = 1;
            arr[i] = v;
            return seg[p];
        }
        int m = (l + r) / 2;
        // Recursive call to update subtrees
        return seg[p] = Merge(Set(i, v, 2 * p, l, m), Set(i, v, 2 * p + 1, m + 1, r));
    }
 
    public Item Set(int i, int v)
    {
        return Set(i, v, 1, 0, n - 1);
    }
 
    private Item Query(int lx, int rx, int p, int l, int r)
    {
        // Query the segment tree within range lx and rx
        if (lx > r || l > rx) return NEUTRAL;
        if (lx <= l && r <= rx) return seg[p];
        int m = (l + r) / 2;
        // Recursive call to query subtrees
        return Merge(Query(lx, rx, 2 * p, l, m), Query(lx, rx, 2 * p + 1, m + 1, r));
    }
 
    public long Query(int lx, int rx)
    {
        return Query(lx, rx, 1, 0, n - 1).invCount;
    }
}
 
class MainClass
{
    public static void Main(string[] args)
    {
        int[] arr = { 1, 2, 3, 6, 5, 4 };
        List<int[]> queries = new List<int[]>
        {
            new int[] { 1, 1, 3 },
            new int[] { 1, 2, 5 },
            new int[] { 2, 2, 8 },
            new int[] { 1, 1, 4 }
        };
 
        SegmentTree st = new SegmentTree(arr);
 
        foreach (var query in queries)
        {
            int type = query[0];
            if (type == 1)
            {
                int l = query[1] - 1;
                int r = query[2] - 1;
                // Query for inversions within the specified range
                Console.WriteLine(st.Query(l, r));
            }
            else if (type == 2)
            {
                int i = query[1] - 1;
                // Set a new value at the specified index
                st.Set(i, query[2]);
            }
        }
    }
}


Javascript




class Item {
    constructor(maxV) {
        this.invCnt = 0;
        this.freq = Array(maxV).fill(0);
    }
}
class GFG {
    constructor(arrVal) {
        this.NEUTRAL = new Item(45);
        this.n = arrVal.length;
        this.arr = arrVal.slice(); // Create a copy of the input array
        this.seg = Array(4 * this.n).fill().map(_ => new Item(45)); // Initialize segment tree array
        this.build(1, 0, this.n - 1);
        // Build the segment tree
    }
    merge(left, right) {
        const parent = new Item(left.freq.length);
        parent.invCnt = left.invCnt + right.invCnt;
        const prev = Array(left.freq.length).fill(0);
        prev[0] = right.freq[0];
        for (let i = 0; i < left.freq.length; i++) {
            parent.freq[i] = left.freq[i] + right.freq[i];
            if (i !== 0) {
                parent.invCnt += 1 * left.freq[i] * prev[i - 1];
                prev[i] = prev[i - 1] + right.freq[i];
            }
        }
        return parent;
    }
    build(p, l, r) {
        if (l === r) {
            // Leaf node initialization
            this.seg[p].invCnt = 0;
            this.seg[p].freq[this.arr[l]] = 1;
            return this.seg[p];
        }
        const m = Math.floor((l + r) / 2);
        // Recursive call to build left and right subtrees
        this.seg[p] = this.merge(this.build(2 * p, l, m), this.build(2 * p + 1, m + 1, r));
        return this.seg[p];
    }
    buildTree() {
        return this.build(1, 0, this.n - 1);
    }
    set(i, v, p, l, r) {
        if (i < l || i > r) return this.seg[p];
        if (l === r) {
            this.seg[p].freq[this.arr[i]] = 0;
            this.seg[p].freq[v] = 1;
            this.arr[i] = v;
            return this.seg[p];
        }
        const m = Math.floor((l + r) / 2);
        // Recursive call to update subtrees
        this.seg[p] = this.merge(this.set(i, v, 2 * p, l, m), this.set(i, v, 2 * p + 1, m + 1, r));
        return this.seg[p];
    }
    setValue(i, v) {
        return this.set(i, v, 1, 0, this.n - 1);
    }
    query(lx, rx, p, l, r) {
        if (lx > r || l > rx) return this.NEUTRAL;
        if (lx <= l && r <= rx) return this.seg[p];
        const m = Math.floor((l + r) / 2);
        // Recursive call to query subtrees
        return this.merge(this.query(lx, rx, 2 * p, l, m), this.query(lx, rx, 2 * p + 1, m + 1, r));
    }
    queryRange(lx, rx) {
        return this.query(lx, rx, 1, 0, this.n - 1).invCnt;
    }
}
// Driver code
const arr = [1, 2, 3, 6, 5, 4];
const queries = [
    [1, 1, 3],
    [1, 2, 5],
    [2, 2, 8],
    [1, 1, 4]
];
const st = new GFG(arr);
for (const query of queries) {
    const type = query[0];
    if (type === 1) {
        const l = query[1] - 1;
        // Making Zero based indexing
        const r = query[2] - 1;
        // Making Zero based indexing
        console.log(st.queryRange(l, r));
    } else if (type === 2) {
        const i = query[1] - 1;
        // Making Zero based indexing
        const v = query[2];
        st.setValue(i, v);
    }
}


Output

0
1
2











Time Complexity: O(n + q*logn)
Auxiliary Space: O(n)



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads