如何用懒惰的传播实现细分树
How can I implement segment trees with lazy propagation?
我正在实现细分树,以便能够快速回答数组中的以下查询:
- 查询i,j:范围内所有元素的总和(i,j)
- 更新I,J,K:将k添加到范围内的所有元素(i,j)
这是我的实现:
typedef long long intt;
const int max_num=100000,max_tree=4*max_num;
intt A[max_num],ST[max_tree];
void initialize(int node, int be, int en) {
if(be==en) {
ST[node]=ST[be];
} else {
initialize(2*node+1,be,(be+en)/2);
initialize(2*node+2,(be+en)/2+1,en);
ST[node]=ST[2*node+1]+ST[2*node+2];
}
}
void upg(int node, int be, int en, int i, intt k) {
if(be>i || en<i || be>en) return;
if(be==en) {
ST[node]+=k;
return;
}
upg(2*node+1, be, (be+en)/2, i, k);
upg(2*node+2, (be+en)/2+1, en, i, k);
ST[node] = ST[2*node+1]+ST[2*node+2];
}
intt query(int node, int be, int en, int i, int j) {
if(be>j || en<i) return -1;
if(be>=i && en<=j) return ST[node];
intt q1=query(2*node+1, be, (be+en)/2, i, j);
intt q2=query(2*node+2, (be+en)/2+1, en, i, j);
if(q1==-1) return q2;
else if(q2==-1) return q1;
else return q1+q2;
}
查询函数确实很快,其复杂性是O(lg n),其中n为j-i。在平均情况下,更新功能也很快,但是当J-I很大时,更新的复杂性是O(n Lg n),这根本不是快速的。
我已经搜索了一些主题,我发现,如果我用懒惰的传播实现片段树,则查询和更新的复杂性是O(lg n),它的渐近速度比O(n lg n)快。。
我还找到了一个指向另一个问题的链接,该链接具有一个非常好的细分树的实现,该片段使用了指示:如何用懒惰的传播实现细分树。因此,这是我的问题:是否有一种更简单的方法来实现懒惰的传播,而无需使用指针,但使用数组索引,没有segment_tree
数据结构?
这是我玩这个数据结构和一些模板tomfoolery的玩。
在所有这些混乱的底部,都访问了两个平坦的阵列,其中一个包含一棵总和,另一个包含一棵携带值的树,以稍后向下传播。从概念上讲,它们形成一棵二进制树。
二进制树中节点的真实值是存储的sum树中的值,以及节点时间下的叶子数量,从节点返回到根部的所有携带树值的总和。
同时,树中每个节点的真实值等于其下面的每个叶子节点的真实值。
我写了一个既可以进行携带又有总和的功能,因为事实证明他们正在访问相同的节点。阅读有时会写。因此,您可以通过用零increase
调用它来获得总和。
所有模板都在做数学,以便对节点的偏移量以及左右孩子的位置进行数学。
当我使用struct
时,struct
是瞬态 - 它只是一个包装器,在偏移量中的偏移量周围有一些预算的值。我确实存储了一个指向数组开始的指针,但是每个block_ptr
在此程序中使用完全相同的root
值。
用于调试,我有一些闪烁示见()和debug()宏,以及递归总和函数呼叫的跟踪零函数(我用来跟踪呼叫的总数)。再一次,不必要的复杂以避免全球状态。:)
#include <memory>
#include <iostream>
// note that you need more than 2^30 space to fit this
enum {max_tier = 30};
typedef long long intt;
#define Assert(x) (!(x)?(std::cout << "ASSERT FAILED: (" << #x << ")n"):(void*)0)
#define DEBUG(x)
template<size_t tier, size_t count=0>
struct block_ptr
{
enum {array_size = 1+block_ptr<tier-1>::array_size * 2};
enum {range_size = block_ptr<tier-1>::range_size * 2};
intt* root;
size_t offset;
size_t logical_offset;
explicit block_ptr( intt* start, size_t index, size_t logical_loc=0 ):root(start),offset(index), logical_offset(logical_loc) {}
intt& operator()() const
{
return root[offset];
}
block_ptr<tier-1> left() const
{
return block_ptr<tier-1>(root, offset+1, logical_offset);
}
block_ptr<tier-1> right() const
{
return block_ptr<tier-1>(root, offset+1+block_ptr<tier-1>::array_size, logical_offset+block_ptr<tier-1>::range_size);
}
enum {is_leaf=false};
};
template<>
struct block_ptr<0>
{
enum {array_size = 1};
enum {range_size = 1};
enum {is_leaf=true};
intt* root;
size_t offset;
size_t logical_offset;
explicit block_ptr( intt* start, size_t index, size_t logical_loc=0 ):root(start),offset(index), logical_offset(logical_loc)
{}
intt& operator()() const
{
return root[offset];
}
// exists only to make some of the below code easier:
block_ptr<0> left() const { Assert(false); return *this; }
block_ptr<0> right() const { Assert(false); return *this; }
};
template<size_t tier>
void propogate_carry( block_ptr<tier> values, block_ptr<tier> carry )
{
if (carry() != 0)
{
values() += carry() * block_ptr<tier>::range_size;
if (!block_ptr<tier>::is_leaf)
{
carry.left()() += carry();
carry.right()() += carry();
}
carry() = 0;
}
}
// sums the values from begin to end, but not including end!
// ie, the half-open interval [begin, end) in the tree
// if increase is non-zero, increases those values by that much
// before returning it
template<size_t tier, typename trace>
intt query_or_modify( block_ptr<tier> values, block_ptr<tier> carry, int begin, int end, int increase=0, trace const& tr = [](){} )
{
tr();
DEBUG(
std::cout << begin << " " << end << " " << increase << "n";
if (increase)
{
std::cout << "Increasing " << end-begin << " elements by " << increase << " starting at " << begin+values.offset << "n";
}
else
{
std::cout << "Totaling " << end-begin << " elements starting at " << begin+values.logical_offset << "n";
}
)
if (end <= begin)
return 0;
size_t mid = block_ptr<tier>::range_size / 2;
DEBUG( std::cout << "[" << values.logical_offset << ";" << values.logical_offset+mid << ";" << values.logical_offset+block_ptr<tier>::range_size << "]n"; )
// exatch math first:
bool bExact = (begin == 0 && end >= block_ptr<tier>::range_size);
if (block_ptr<tier>::is_leaf)
{
Assert(bExact);
}
bExact = bExact || block_ptr<tier>::is_leaf; // leaves are always exact
if (bExact)
{
carry()+=increase;
intt retval = (values()+carry()*block_ptr<tier>::range_size);
DEBUG( std::cout << "Exact sum is " << retval << "n"; )
return retval;
}
// we don't have an exact match. Apply the carry and pass it down to children:
propogate_carry(values, carry);
values() += increase * end-begin;
// Now delegate to children:
if (begin >= mid)
{
DEBUG( std::cout << "Right:"; )
intt retval = query_or_modify( values.right(), carry.right(), begin-mid, end-mid, increase, tr );
DEBUG( std::cout << "Right sum is " << retval << "n"; )
return retval;
}
else if (end <= mid)
{
DEBUG( std::cout << "Left:"; )
intt retval = query_or_modify( values.left(), carry.left(), begin, end, increase, tr );
DEBUG( std::cout << "Left sum is " << retval << "n"; )
return retval;
}
else
{
DEBUG( std::cout << "Left:"; )
intt left = query_or_modify( values.left(), carry.left(), begin, mid, increase, tr );
DEBUG( std::cout << "Right:"; )
intt right = query_or_modify( values.right(), carry.right(), 0, end-mid, increase, tr );
DEBUG( std::cout << "Right sum is " << left << " and left sum is " << right << "n"; )
return left+right;
}
}
这里有一些辅助类,可以使创建一个给定尺寸的细分树变得容易。但是,请注意,您只需要一个适合大小的数组,您可以从指针到元素0构造block_ptr,并且您可以使用。
template<size_t tier>
struct segment_tree
{
typedef block_ptr<tier> full_block_ptr;
intt block[full_block_ptr::range_size];
full_block_ptr root() { return full_block_ptr(&block[0],0); }
void init()
{
std::fill_n( &block[0], size_t(full_block_ptr::range_size), 0 );
}
};
template<size_t entries, size_t starting=0>
struct required_tier
{
enum{ tier =
block_ptr<starting>::array_size >= entries
?starting
:required_tier<entries, starting+1>::tier
};
enum{ error =
block_ptr<starting>::array_size >= entries
?false
:required_tier<entries, starting+1>::error
};
};
// max 2^30, to limit template generation.
template<size_t entries>
struct required_tier<entries, size_t(max_tier)>
{
enum{ tier = 0 };
enum{ error = true };
};
// really, these just exist to create an array of the correct size
typedef required_tier< 1000000 > how_big;
enum {tier = how_big::tier};
int main()
{
segment_tree<tier> values;
segment_tree<tier> increments;
Assert(!how_big::error); // can be a static assert -- fails if the enum of max tier is too small for the number of entries you want
values.init();
increments.init();
auto value_root = values.root();
auto carry_root = increments.root();
size_t count = 0;
auto tracer = [&count](){count++;};
intt zero = query_or_modify( value_root, carry_root, 0, 100000, 0, tracer );
std::cout << "zero is " << zero << " in " << count << " stepsn";
count = 0;
Assert( zero == 0 );
intt test2 = query_or_modify( value_root, carry_root, 0, 100, 10, tracer ); // increase everything from 0 to 100 by 10
Assert(test2 == 1000);
std::cout << "test2 is " << test2 << " in " << count << " steps n";
count = 0;
intt test3 = query_or_modify( value_root, carry_root, 1, 1000, 0, tracer );
Assert(test3 == 990);
std::cout << "test3 is " << test3 << " in " << count << " stepsn";
count = 0;
intt test4 = query_or_modify( value_root, carry_root, 50, 5000, 87, tracer );
Assert(test4 == 10*(100-50) + 87*(5000-50) );
std::cout << "test4 is " << test4 << " in " << count << " stepsn";
count = 0;
}
虽然这不是您想要的答案,但它可能使某人更容易编写它。写这本书让我很开心。所以,希望它有帮助!
使用C 0x编译器在IDEONE.com上进行了测试和编译。
懒惰的传播意味着仅在需要时更新。它的技术允许使用渐近时间复杂性o(logn)进行范围更新(n这是范围)。
说您要更新范围[0,15],然后您更新节点[0,15],并在节点中设置标志,该标志说要更新它的子节点(使用前哨值,以防万一标志不使用)。
可能的应力测试案例:
0 1 100000
0 1 100000
0 1 100000 ...重复Q次(其中Q = 99999),而100000th查询将为
1 1 100000
在那种情况下
随着懒惰的繁殖,您只需要翻转节点[0,100000] 99999次,并设置/解开一个要更新其子女的标志。当询问实际的查询本身时,您开始穿越孩子并开始翻转他们,将标志向下推,并解开父母的标志。
oh,请确保您使用的是适当的I/O例程(如果其C (如果其C )为CIT和COUT)希望这使您对懒惰的传播的含义有所了解。更多信息:http://www.spoj.pl/forum/viewtopic.php?f=27&t = 8296
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 如何用懒惰的传播实现细分树
- 细分树实现