计算细分中包含更新的转化次数

问题描述

我正在尝试解决如下问题:

问题

Given an array of integers "arr" of size "n",process two types of queries. There are "q" queries you need to answer.

Query type 1
    input: l r
   result: output number of inversions in [l,r]

Query type 2
    input: x y
   result: update the value at arr [x] to y

反转

For every index j < i,if arr [j] > arr [i],the pair (j,i) is one inversion.

输入

n = 5
q = 3
arr = {1,4,3,5,2}

queries:
    type = 1,l = 1,r = 5
    type = 2,x = 1,y = 4
    type = 1,r = 5

输出

4
6

约束

Time: 4 secs

1 <= n,q <= 100000

1 <= arr [i] <= 40

1 <= l,r,x <= n

1 <= y <= 40

我知道如何解决此问题的更简单版本而不进行更新,即使用 O(N * log(N))中的段树或fenwick树简单地计算每个位置的求反数。我对此问题的唯一解决方案是 O(q * N * log(N))(我认为),其中段树不是 O(q * N 2 )简单算法。但是,这不适合问题的时间限制。我想提出一个更好的算法来解决 O(N * log(N))(如果可能)或也许 O(N * log 2 (N))

两天前我第一次遇到这个问题,已经在这里和那里花费了几个小时来尝试解决它。但是,我发现这样做并非易事,并且希望对此有所帮助/提示。感谢您的时间和耐心。

更新

解决方

借助suggestion,answer and help中的Tanvir Wahid,我已经在C ++中实现了该问题的源代码,并希望在这里与那些可能偶然遇到此问题并且没有直观想法的人共享该源代码。如何解决它。谢谢!

让我们构建一个带有每个节点的分段树,其中包含有关存在多少反转以及其授权分段中元素的频率计数的信息。

node {
    integer    inversion_count : 0
    array [40] frequency       : {0...0}
}

构建细分树并处理更新

对于每个叶节点,将反转计数初始化为0,并将表示元素的频率从输入数组增加到1。可以通过将左右两个子节点的频率相加来计算父节点的频率。父节点的反转计数可以通过将左右子节点的反转计数相加得出,再加上在合并其权限的两个部分时创建的新反转,可以使用每个子节点中元素的频率来计算。该计算基本上是找出左子元素中较大元素的频率与右子元素中较小元素的频率的乘积。

parent.inversion_count = left.inversion_count + right.inversion_count
for i in [39,0]
    for j in [0,i)
        parent.inversion_count += left.frequency [i] * right.frequency [j]

更新的处理方式类似。

根据反演计数回答范围查询

要回答有关[l,r]范围内的反转次数查询,我们使用下面随附的源代码计算反转次数

时间复杂度:O(q * log(n))

注意

附带的源代码确实破坏了一些良好的编程习惯。该代码的唯一目的是“解决”给定的问题,而不完成其他任何事情。

代码

/**
    Lost Arrow (Aryan V S)
    Saturday 2020-10-10
**/

#include "bits/stdc++.h"

using namespace std;

struct node {
    int64_t inv = 0;
    vector <int> freq = vector <int> (40,0);

    void combine (const node& l,const node& r) {
        inv = l.inv + r.inv;
        for (int i = 39; i >= 0; --i) {
            for (int j = 0; j < i; ++j) {
                // frequency of bigger numbers in the left * frequency of smaller numbers on the right
                inv += 1LL * l.freq [i] * r.freq [j];
            }
            freq [i] = l.freq [i] + r.freq [i];
        }
    }
};

void build (vector <node>& tree,vector <int>& a,int v,int tl,int tr) {
    if (tl == tr) {
        tree [v].inv = 0;
        tree [v].freq [a [tl]] = 1;
    }
    else {
        int tm = (tl + tr) / 2;
        build(tree,a,2 * v + 1,tl,tm);
        build(tree,2 * v + 2,tm + 1,tr);
        tree [v].combine(tree [2 * v + 1],tree [2 * v + 2]);
    }
}

void update (vector <node>& tree,int tr,int pos,int val) {
    if (tl == tr) {
        tree [v].inv = 0;
        tree [v].freq = vector <int> (40,0);
        tree [v].freq [val] = 1;
    }
    else {
        int tm = (tl + tr) / 2;
        if (pos <= tm)
            update(tree,tm,pos,val);
        else
            update(tree,tr,val);
        tree [v].combine(tree [2 * v + 1],tree [2 * v + 2]);
    }
}

node inv_cnt (vector <node>& tree,int l,int r) {
    if (l > r)
        return node();
    if (tl == l && tr == r)
        return tree [v];
    int tm = (tl + tr) / 2;
    node result;
    result.combine(inv_cnt(tree,l,min(r,tm)),inv_cnt(tree,max(l,tm + 1),r));
    return result;
}

void solve () {
    int n,q;
    cin >> n >> q;
    vector <int> a (n);
    for (int i = 0; i < n; ++i) {
        cin >> a [i];
        --a [i];
    }

    vector <node> tree (4 * n);
    build(tree,n - 1);

    while (q--) {
        int type,x,y;
        cin >> type >> x >> y;
        --x; --y;

        if (type == 1) {
            node result = inv_cnt(tree,n - 1,y);
            cout << result.inv << '\n';
        }
        else if (type == 2) {
            update(tree,y);
        }
        else
            assert(false);
    }
}

int main () {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.precision(10);
    std::cout << std::fixed << std::boolalpha;

    int t = 1;
//  std::cin >> t;
    while (t--)
        solve();

    return 0;
}

解决方法

arr [i]最多可以为40。我们可以利用它来发挥自己的优势。我们需要的是一个段树。每个节点将保存41个值(一个long long int表示该范围的取反,每个数字计数的数组大小为40。将使用结构)。我们如何合并一个节点的两个孩子。我们知道左子和右子的反转。也要知道两个数字中每个数字的频率。父节点的反转将是两个子节点的反转加上左,右子节点之间的反转次数之和。我们可以很容易地从数字的频率中找到两个孩子之间的求逆。查询可以用类似的方式完成。复杂度O(40 * qlog(n))

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...