分段和算法

时间:2014-11-16 23:12:35

标签: c++ arrays algorithm sum time-complexity

我正在尝试解决以下任务:

1)给定大小为N的阵列A. 2)给定范围更新查询的集合,即(L,R,val),对于L< = i< = R,应该 A [i] + = val
3)给定范围和查询的集合,即(L,R),其应返回 sum(A [i]),其中L< = i< = R.

约束:

1)A,段和查询集的大小N,N1,N2&lt; = 2 ^ 24 2)0 <= L <= 2 ^ 24,0 <= R&lt; = 2 ^ 24,0 <= val <= 2 ^ 24。

问题是计算所有范围和查询的总和(S)模2 ^ 32。

似乎可以实现细分树以获得 O(NlogN)时间的所需总和,但实际上我们 需要使用此数据结构。相反,我们可以使用2或3个数组以某种方式在 O(N)时间内计算S.这里的一般想法是什么?

我最近在C ++中为这个问题编写了一些算法,但这并不是最优的。伪代码:

  1. 创建两个数组添加[0..N-1]和Substract [0..N-1]。
  2. 迭代范围更新集并执行Add [L] + = val和Substract [R] + = val。
  3. 创建数组Partial_sum [0..N]
  4. Partial_sum [0] = 0,what_to_add = 0.
  5. 对于[1..N]中的我:
    5.1。 Partial_sum [i] = Partial_sum [i - 1] +添加[i - 1] + what_do_add
    5.2。 what_do_add = what_to_add +添加[i - 1] - Substract [i - 1]
  6. 我们得到 Partial_sum 数组,可以轻松计算 O(1)时间内的任何段和(L,R),就像 Partial_sum [R + 1]一样 - Partial_sum [L]

    但问题是第2步太慢了。而且,步骤5中的循环很难解决。那是O(n)解,但常数太高。我知道应该有改进步骤5的方法,但我不知道如何做到这一点。

    有人可以提出一些想法,甚至建议自己的算法来解决这个问题吗?

    谢谢。

    我的算法实现:

    #include <cstring>
    #include <iostream>
    #include <stdio.h>
    
    typedef unsigned int UINT;
    typedef unsigned long long ULL;
    
    
    //MOD and size of A
    const ULL MOD  = 4294967296LL; // 2^32
    const size_t N = 16777216;     // 2^24
    
    //params for next_rand()
    UINT seed = 0;
    UINT a;
    UINT b;
    
    
    //get random segment
    UINT next_rand()
    {
        seed = seed * a + b;
        return seed >> 8;
    }
    
    
    int main()
    {
        UINT N1, N2;
    
        std::cin >> N1 >> N2;
        std::cin >> a >> b;
    
        UINT* add  = new UINT[N];         //Add array
        UINT* subs = new UINT[N];         //Substraction array
        UINT* part_sum = new UINT[N + 1]; //Partial sums array
    
        memset(add, 0, sizeof(UINT) * N);
        memset(subs, 0, sizeof(UINT) * N);
        memset(part_sum, 0, sizeof(UINT) * (N + 1));  //Initialize arrays
    
        //step 2 
        for (size_t i = 0; i < N1; ++i)
        {
            UINT val = next_rand();
            UINT l   = next_rand();
            UINT r   = next_rand();
    
            if (l > r)
            {
                std::swap(l, r);
            }
    
            add[l]  = (add[l] + val);
            subs[r] = (subs[r] + val);
        }
    
        part_sum[0]   = 0;
        UINT curr_add = 0;
    
        //step 5
        for (size_t i = 1; i <= N; ++i)
        {
            part_sum[i] = (part_sum[i - 1] + curr_add + add[i - 1]);
    
            curr_add = (curr_add + add[i - 1] - subs[i - 1]);
        }
    
        UINT res_sum = 0;
    
        //Get any segment sum in O(1)
        for (size_t i = 0; i < N2; ++i)
        {
            UINT l = next_rand();
            UINT r = next_rand();
    
            if (l > r)
            {
                std::swap(l, r);
            }
            res_sum = (res_sum + part_sum[r + 1] - part_sum[l]);
        }
    
        std::cout << res_sum;
    
        delete []add;
        delete []subs;
        delete []part_sum;
    
        return 0;
    }
    

2 个答案:

答案 0 :(得分:1)

我以不同的方式实现了所描述的算法。它应该更快。在更新和求和查询大小的最大值时,它应该比以前更快。

#include <iostream>
#include <stdio.h>
#include <vector>

typedef unsigned int UINT;
typedef unsigned long long ULL;

const ULL MOD  = 4294967296LL; // 2^32
const size_t N = 16777216;     // 2^24

UINT seed = 0;
UINT a;
UINT b;

UINT next_rand()
{
    seed = seed * a + b;

    return seed >> 8;
}

std::vector <std::pair<UINT, UINT> > add;

int main()
{
    UINT upd_query_count;
    UINT sum_query_count;

    // freopen("fastadd.in",  "r", stdin);
    // freopen("fastadd.out", "w", stdout);

    scanf("%u", &upd_query_count);
    scanf("%u", &sum_query_count);
    scanf("%u", &a);
    scanf("%u", &b);

    add.reserve(N+1);

    for (size_t i = 0; i < upd_query_count; ++i)
    {  
        UINT val = next_rand();
        UINT l   = next_rand();
        UINT r   = next_rand();

        if (l > r)
        {
            add[r].first     += val;
            add[l + 1].first -= val;
        }
        else
        {
            add[l].first     += val;
            add[r + 1].first -= val;
        }
    }

    for (size_t i = 0; i < sum_query_count; ++i)
    {
        UINT l = next_rand();
        UINT r = next_rand();

        if (l > r)
        {
            ++add[r].second;
            --add[l + 1].second;
        }
        else
        {
            ++add[l].second;
            --add[r + 1].second;
        }
    }

    UINT curr_add = 0;
    UINT res_sum  = 0;
    UINT times    = 0;

    for (size_t i = 0; i < N; ++i )
    {
        curr_add += add[i].first;
        times    += add[i].second;

        res_sum += curr_add * times;
    }

    printf("%u\n", res_sum);

    return 0;
}

答案 1 :(得分:0)

所以add ad subs非常大数组。

你应该在这里寻找加速的第一个地方是内存访问。当N1变大时,最终会出现大量缓存未命中。这可能有点超出了解释范围,所以我将链接:http://en.wikipedia.org/wiki/CPU_cache

就你的方式而言,你可以加快速度。让我们通过命令我们的访问来尝试改善空间平等。

std::vector<std::pair<UINT, UINT>> l{N1};
std::vector<std::pair<UINT, UINT>> r{N1};

for(size_t i = 0; i < N1; ++i){
    const UINT val = next_rand();
    const UINT first = next_rand();
    const UINT second = next_rand();

    if(first > second){
        l[i] = std::make_pair(second, val);
        r[i] = std::make_pair(first, val);
    }else{
        l[i] = std::make_pair(first, val);
        r[i] = std::make_pair(second, val);
    }
}
std::sort(l.begin(), l.end());
std::sort(r.begin(), r.end());

for(size_t i = 0; i < N1; ++i){
    add[l[i].first] += l[i].second;
    subs[r[i].first] += r[i].second;
}

记住一些事项,std::pair的{​​{1}}比较operator<元素,如果相等则比较first。这就是我可以在不编写任何代码的情况下使用second的方法。但是,如果std::sort对于两个元素相等,则最高first将始终是添加的第二个元素。在您当前的代码中,这似乎不是一个问题,但如果它成为一个问题,您可以通过编写自己的排序循环来解决它,而不是依赖val

此外,根据每个缓存块的稀疏访问方式,在单独的循环中进行添加可能会更快。

一如既往,真正提高性能的唯一方法就是使用实际数字时,所以一定要做自己的替补标记作为比较方法。