如何在二进制索引树或Fenwick树中进行范围更新?

时间:2013-08-05 18:33:27

标签: algorithm fenwick-tree

我正在尝试使用二进制索引树解决UVA中的this问题:

Problem H
Ahoy, Pirates!

Input: Standard Input
Output: Standard Output


In the ancient pirate ages, the Pirate Land was divided into two teams of pirates,  
namely, the Buccaneer and the Barbary pirates. Each pirate’s team was not fixed,  
sometimes the opponent pirates attacked and he was taken away to the other pirate team.  
All on a sudden a magician appeared in the Pirate Land, where he was making transition  
of pirates from their team to other team at his own will. Of course, handy spells  
were used. The process of changing team was known as mutating.

There were N pirates and all of the pirates have a unique id from 0 to N-1.  
The great magician could mutate a bunch of pirates with consecutive id’s to  
another one.

Suppose there were 100 pirates in the pirate land and all of them were  
Barbary pirates, then the magician could cast a spell to change pirates with  
id’s from 10 to 33 to Buccaneer pirates. Then the whole pirate land would  
have 24 Buccaneer and 76 Barbary pirates.

The magician was very fast casting the spell. Once, God started to dislike this.  
God had favor for the Buccaneer pirates and God asked the magician, “Tell me,  
how many of the pirates of index from 2 to 30 are Buccaneer pirates?”. Now the  
magician was puzzled as he was only efficient in casting spells, not in counting J

Being clever enough, the magician captured a clever man from the Earth Land.  
And unfortunately it’s you! Now you have to answer the God’s questions.


Input

The first line of input will contain number of test cases T.

For each test case:
The first part of the description will be of the pirate land.  
There could be up to N (1<=N<=1024000) pirates. Each pirate is either  
assigned to Buccaneer or Barbary Pirate. Buccaneer pirates are described  
by ‘1’ (ONE) and Barbary pirates are described by ‘0’ (ZERO). You have to  
build a string of the pirates’ description. Each case starts with an integer  
M (M<=100), where M pair lines follow. In each pair of lines (we call it a set),  
first has an integer T  (T <= 200) and next one has a nonempty string Pirates  
(consisting of 0 and 1, 0 for Barbary, 1 for Buccaneer, has maximum length of 50).  
For each pair concatenate the string Pirates, T times. Concatenate all the  
resulting M sets of strings to build the pirate description. The final  
concatenated string describes the pirates from index 0 to end (N-1 for N pirates).

Now the next part of the input will contain queries.  
First line of next part has an integer Q describing number of queries.  
Each subsequence Q (1<=Q<=1000) lines describe each query. Each query has a  
string F or E or I or S and two integers, a and b denoting indexes.  
The meaning of the query string are follows:

F a b, means, mutate the pirates from index a to b to Buccaneer Pirates.
E a b, means, mutate the pirates from index a to b to Barbary Pirates.
I a b, means, mutate the pirates from index a to b to inverse pirates.
S a b, means, “God’s query” God is asking a question:  
“Tell me, how many Buccaneer pirates are there from index a to b?”
(a <= b, 0 <= a < n, 0 <= b < n, index range are inclusive)

Output
For each test print the case number as the sample output suggests.  
Then for each of “God’s query”, output the query number, colon (:) and  
a space and the answer to the query as the sample suggest.

╔══════════════╦═════════════════════════╗
║ Sample Input ║ Output for Sample Input ║
╠══════════════╬═════════════════════════╣
║ 2            ║ Case 1:                 ║
║ 2            ║ Q1: 5                   ║
║ 5            ║ Q2: 1                   ║
║ 10           ║ Case 2:                 ║
║ 2            ║ Q1: 0                   ║
║ 1000         ║                         ║
║ 5            ║                         ║
║ F 0 17       ║                         ║
║ I 0 5        ║                         ║
║ S 1 10       ║                         ║
║ E 4 9        ║                         ║
║ S 2 10       ║                         ║
║ 3            ║                         ║
║ 3            ║                         ║
║ 1            ║                         ║
║ 4            ║                         ║
║ 0            ║                         ║
║ 2            ║                         ║
║ 0            ║                         ║
║ 2            ║                         ║
║ I 0 2        ║                         ║
║ S 0 8        ║                         ║
╚══════════════╩═════════════════════════╝


Explanation:

Case1:
The pirate land is as follows (N = 18)
101010101010001000

Before God’s first query it was as follows
000000111111111111

Case 2:
The pirate land is as follows (N=9)
111000000

它是关于将一​​系列值递增或递减为1或0,并且查询在fenwick树中一如既往。
我知道如何更新树中的特定位置 更新范围时,我只是为该范围内的每个元素使用了一个循环来更新为1或0.但它花费了太多时间。

还是有其他方法可以在fenwick树中进行范围更新吗?

实际上它是一个1'和0的数组,没有别的,更新程序只包括将范围[a,b]更新为1,0或反转。 这是我的代码:

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

using namespace std;
typedef vector<int> vi;

class bit{
public:
bit(int n,string strt):s(strt){ ftBuc.assign(n+2,0); }
void adjust(int k,int v){

if(v=='1'&&s[k] == '1')
return;
else if(v=='0' && s[k] == '0')
return;
else if(v=='1'&&s[k] == '0')
{ s[k] = '1';   for(;k<(int)ftBuc.size();k += leastSig1(k)) ftBuc[k]+=1; }
else if(v=='0' && s[k] == '1' )
{ s[k] = '0';   for(;k<(int)ftBuc.size();k += leastSig1(k)) ftBuc[k]-=1; }
}
int rsq(int a,int b) { return rsq(b)-(a==1?0:rsq(a-1));}
string s;
int rsq(int b) { int sum = 0; for(; b ; b-=leastSig1(b)) sum+=ftBuc[b]; return sum;}
vi ftBuc;
int leastSig1(int i) {return i&(-i);}

};
int main()
{
int t;
cin>>t;
for(int test=1;test<=t;++test)
{   
int m;
cin>>m;
string s;
while(m--)
{
int T;
cin>>T;
string temp;
cin>>temp;
while(T--)
s.append(temp);
}
int n = s.size();
bit ft(n+2,string(n+2,'0'));
for(int i = 1;i<=n;++i)
ft.adjust(i,s[i-1]);
int q;
cin>>q;
int asks = 1;
printf("Case %d:\n",test);
while(q--)
{
char c;
int i,j;
cin>>c>>i>>j;
if(c=='F')
{
for(int k=i+1;k<=j+1;++k)
ft.adjust(k,'1');    
}
else if(c=='E')
{
for(int k=i+1;k<=j+1;++k)
ft.adjust(k,'0');
}
else if(c == 'I')
{
for(int k=i+1;k<=j+1;++k)
{
if(ft.s[k] == '1')
ft.adjust(k,'0');
else
ft.adjust(k,'1');

}
}
else if(c=='S')
printf("Q%d: %d\n",asks++,ft.rsq(i+1,j+1));

}

}       
}

2 个答案:

答案 0 :(得分:6)

BIT可以以下列三种模式之一运行:

  • 点更新和范围查询
  • 范围更新和点查询
  • 范围更新和范围查询

我已经用BIT解释了范围更新,并在此处提供了实施:http://kartikkukreja.wordpress.com/2013/12/02/range-updates-with-bit-fenwick-tree/

答案 1 :(得分:3)

二进制索引树(又名Fenwick树)是表示频率表的有效方式,以便能够快速提取累积频率并使用N个频段来保存N个频率值。

但是,对于这个问题,我建议改为segment tree。段树具有与二进制索引树类似的结构,除了它通常使用2N个箱。

Fenwick树

与Fenwick树一样,考虑树中的每个节点负责一组索引是有帮助的。以下是大小为8的Fenwick树中节点职责的说明:

Index   Responsibilities
  8              8
  7        7     8
  6          6   8 
  5        5 6   8
  4            4 8
  3        3   4 8
  2          2 4 8
  1        1 2 4 8

这意味着,例如,Fenwick条目A [4]将负责指数1,2,3,4和A [4]将包含总频率F [1] + F [2] + F [3] + F [4]。

细分树

相反,细分树中的职责如下:

Index   Responsibilities
  8        1 3 7 15   
  7        1 3 7 14     
  6        1 3 6 13    
  5        1 3 6 12    
  4        1 2 5 11  
  3        1 2 5 10    
  2        1 2 4 9  
  1        1 2 4 8 

延迟更新

那我们为什么要这样做呢?

原因是我们现在有一个单独的条目负责每个细分范围,这让我们可以使用延迟更新。

延迟更新的想法是,当您希望更新一系列值时,您只需要递归到段树并细分,直到达到完全包含在要更新的范围内的间隔。一旦达到这样的间隔,你就会做两件事:

  1. 更新该时间间隔的值
  2. 在该节点存储信息,以指示下方的段仍需要更新
  3. 请注意,在递归过程中,您还需要推迟遇到的任何延迟操作。

    例如代码,尝试搜索“segment tree”和“lazy propagation”。

    复杂性

    具有延迟传播的段树将花费O(log(N))来查询和更新任何值范围。