我正在尝试解决以下任务:
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 ++中为这个问题编写了一些算法,但这并不是最优的。伪代码:
我们得到 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;
}
答案 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
。
此外,根据每个缓存块的稀疏访问方式,在单独的循环中进行添加可能会更快。
一如既往,真正提高性能的唯一方法就是使用实际数字时,所以一定要做自己的替补标记作为比较方法。