Fenwick tree sets of weights segment tree

1 Introduction

Fenwick tree sleeve segment tree may be \ (O (nlogn) \) maintaining excellent complexity modify operation interval smaller value K band and the band modify the operation interval greater than / less than the number of values of K problems.

Some people are planting this tree structure is called the tree cover an array of sets Chairman tree . In fact, this tree cover tree, every one inner segment tree is an independent , not like persistable line tree (widely accepted "Chairman tree") as "interdependent" the tree line. However, due to the "Chairman tree" in \ (OI \) boundary is not clearly defined and, in some context can also dynamically open point Chairman of the tree known as the tree line paper for uniform application of the inner layer of the tree "segment tree / dynamic segment tree open point" in the title.

2. Pre-knowledge(Trees have learned how to set the tree may not)

3. Principle

Position range from 3.1 to

The number of columns in the maintenance of a data structure, we usually see two maintenance mode:

3.1.1 maintenance mode \ (1 \)

The position of the standard, the value content , such as the most basic segment tree, when we execute a query operations, such as query [3,8], is obtained , "the number of columns in the original number of 3 to 8 the number" one of some information (and / best value) and the like.

3.1.2 maintenance mode \ (2 \)

Subscripts range as to the number of occurrence values for the content , such as a request for reverse Fenwick tree, if the query [3,8], the result is the number of occurrences of the number of values in [3,8] within .

We adopt maintenance mode \ (2 \) of the segment tree is called the weights tree line , according to the tree line comes with the "two" property (each node two into the left child node and the right child node), we can use weights segment tree to solve the dynamic global \ (K \) small value problem.

Prefix and from 3.2 to Fenwick tree

3.2.1 problem \ (1 \)

First, let's think about a very simple question: to give you a number of columns, not to modify , interval and repeatedly asked, how do?

Too simple! Prefix and engage in a practice it.

Specifically, a length of open \ (n-\) of the array (referred to as \ (A \) ), \ (a_i \) Maintenance \ (1 \) digit into the first \ (I \) digits and , then to query \ ([L, R] \ ) of this section and, just use \ (a_R \) subtracting \ (a_ {L-1} \) on it.

3.2.2 problem \ (2 \)

One more question: to give you a number of columns, not to modify , asking small section of the K value many times, how do?

Yes! It is the static interval \ (K \) is small, the Chairman of the template title tree!

too easy!Chairman of the tree engage in a practice on it!

Here it is necessary to understand the President of trees seeking the static section K small value principle is actually. Prefixes and thought: On \ (n \) stars weights tree line , the \ (i \) satellites Maintenance \ (1 \) digits into the first \ (i \) digit range information, then you want to query \ ([L, R] \ ) the weight of the interval \ (K \) small value, just use the first \ (R \) weight pieces segment tree subtracting \ (L-1 \) particle weight segment tree, then the above \ (3.1.2 \) a method solve \ ([L, R] \ ) interval \ (K \) small value.

That open \ (n \) pieces of weight segment tree will burst space how do? Persistable just fine.

I can not read? Go back and review the static interval \ (K \) small

3.2.3 problem \ (3 \)

Give you a number of columns, side edge modified asked , repeatedly asked the interval and, how do?

Too simple! Fenwick tree maintenance prefix and engage in a practice it.

Specifically, opened a length \ (n \) array (denoted \ (c \) ), that is, the array tree array. If you want to query \ ([1, i] \ ) prefix and only need to be no more than \ (log_2i \) a \ (c \) values add up to it. when changes only need to modify more than (log_2i \) \ a \ (c \) value. complexity \ ( O (log_2n) \) .

I can not read? Go back and review Fenwick tree

3.2.4 problem \ (4 \)

Give you a number of columns, side edge modified ask , ask small section of the K value many times, how do?

Yes! It is the dynamic range \ (K \) template issue or value!

Combined \ (3.2.2 \) and \ (3.2.3 \) thinking, we can open \ (n \) stars weights tree line, with Fenwick tree maintenance ( node weights segment tree is equivalent to an array of tree ).

If you want to query the interval \ ([1, i] \ ) information range, just take no more than \ (log_2i \) pieces add up to tree line on it. So, if you want to query the interval \ ([L, R & lt] \) of the range information, let the \ (log_2i \) teeth combined segment tree request \ ([1, R] \ ) information, then the \ (log_2i \) stage segment tree pieces together seek \ ([1, L-1 ] \) information, and then \ ([1, R] \ ) information subtract \ ([1, L-1 ] \) information, like \ (3.2.2 \ ) then seek it. (I do not know how to add or how demand? Go back and read \ (3.2.2 \)).

Modify the time, only need to modify more than \ (log_2i \) stars tree line. Modify \ (1 \) pieces segment tree takes time \ (O (log_2n) \) , so \ (1 \) times to modify the total time is \ (O (log_2 ^ 2n) \) .

The complexity of the changes seem right, but the query adding that step, cumulative \ (1 \) stars get to be \ (O (the n-) \) , but also to accumulate \ (log_2i \) satellites, to achieve a single complexity a \ (O (nlogn) \) . how to do? the next section we say.

Here, we have solved just learning Fenwick tree sets the tree line (For example, at the time I) A very tangled issue - the inner layer of the tree line memory what ?

I ask you: that array tree array What is the deposit?

It is not know what to say, only you can not write ?

Yes, here's something to keep tree line similar to that array saved something.

4. The code implementation

4.1 discrete

Easy to find, in our inner segment tree is based on the weight of the range, if the range value of the subject is too large, the need for discrete. Belt operation modifications, also need to modify the input value in, together with the initial weight discrete .

4.2 modify operations

And dynamically modify the general prescription segment tree of the same. If the node is a new node into the air. Choose to enter one of the left and right to modify sub-tree.

4.2.1 Sample Code
   //在内层线段树中
    void change(int &x,int L,int R,int Pos,int k)
    {
        if(x==0)x=++Tot;
        v[x]+=k;
        if(L==R)return;
        int Mid=(L+R)>>1;
        if(Pos<=Mid)change(LC[x],L,Mid,Pos,k);
        else change(RC[x],Mid+1,R,Pos,k);
    }
   //在外层树状数组中
    void change(int p,int val,int v)
    {
        for(int i=p;i<=n;i+=i&-i)
            SegmentTree.change(SegmentTree.Root[i],1,n,val,v);
    }
4.2.2 Note

It is worth noting that,If the above analysis to understand the words, You will find an array of the outer layer of the tree is the location for the next subject. This is why we need to pass both the outer layer of the tree when modifying an array subscript position (code of \ (the p-\) ), but also pass a value (that is, subscript inner segment tree, the code \ (Val \) ) reasons.

4.3 weight adding method segment tree

In order to optimize the \ (3.2.4 \) "referred to in the tree line add up to this point high complexity of the issue, we have two ideas:

4.3.1 calculated separately, the cumulative answer

Sometimes, the subject of the inquiry is not the (relative) rankings , but the (absolute) value , that is, the dynamic range of the big inquiry / less than \ (K \) of the number of values we find in solving this problem, each segment tree pieces are separate. in other words, we do not need to really put together these segments trees , just calculated that in each segment tree \ (1 \) answers pieces of tree line , then this \ ( logn \) answers stars tree line add up on it. in this case, every single tree line query \ (1 \) times is \ (O (logn) \) , a total of \ (O (logn) \) pieces tree line, the total complexity of a single query is \ (O (log_2 ^ 2n) \) .

We did not understand images to help understand?:

The original idea :

Calculated separately, the cumulative answer of ideas:

Sample code :

    //内层线段树
    int query(int x,int L,int R,int X,int Y)
    {
        if(x==0||L>Y||R<X)return 0;
        if(L>=X&&R<=Y)return val[x];
        int Mid=(L+R)>>1;
        return query(LC[x],L,Mid,X,Y)+query(RC[x],Mid+1,R,X,Y);
    }
    //外层树状数组
    int sum(int p,int Lx,int Rx)
    {
        int x=0;
        for(int i=p;i;i-=i&-i)x+=SegmentTree.query(SegmentTree.Root[i],1,n,Lx,Rx);
        return x;
    }
    int query(int L,int R,int Lx,int Rx)
    {
        if(Lx>Rx||L>R)return 0;
        return sum(R,Lx,Rx)-sum(L-1,Lx,Rx);
    }
4.3.2 recording node, are considered active

如果我们求的是排名呢?只有把所有的数据汇总到一起才能得到总排名,显然不能向上面那样对于每颗树单独算再相加.

假设我们已经得到了这\(logn\)颗线段树的和,现在我们要利用这个和线段树来计算答案.

容易发现,在每一个节点中,我们是进入左子节点还是右子节点,只与左子节点的大小与\(K\)的大小的关系有关(不知道为什么?回去看主席树求静态区间K小值),与树中其它任何节点都无关,这启发我们在要用到某个节点的数据的时候,再对这个节点求和.举个例子,现在我们在假想的和线段树中到了节点\(u\),需要通过\(size[LC[u]]\)的大小来判断是进入左子树还是右子树,那么我们当场从那\(logn\)颗子树中揪出对应的\(LC[u]\)这个节点,现场求和,并判断进入左子树还是右子树.

如果可以在\(O(1)\)时间内揪出,显然复杂度也是\(O(log_2^2n)\)的.

那么怎么个法呢?聪明的你一定可以想到,我们只需要在开始遍历这颗假想的和线段树之前,用一个数组存一下这\(logn\)颗线段树的根节点,即"应该揪出的节点的编号",然后每次进入左子树时,把"应该揪出的节点的编号"指向其左儿子,进入右子树则指向其右儿子.这样就可以保证\(O(1)\)揪出了.

没看懂?再来看图片解释:

需要注意的是,我们现在求的是\([L,R]\)区间,所以要进行现场加上\(1...R\)\(logn\)颗子树现场减去\(1...L-1\)\(logn\)颗子树两步操作.

示例代码给出的是求区间\(K\)小值,显然我们可以把它延伸到求区间大于/小于\(K\)的值的个数的问题.

示例代码

    //内层线段树
    int Query(int L,int R,int K)
    {
        if(L==R)return L;
        int sum=0;
        for(int i=1;i<=C1;i++)sum-=v[LC[X[i]]];//现场减去1...L-1那logn颗子树
        for(int i=1;i<=C2;i++)sum+=v[LC[Y[i]]];//现场加上1...R那logn颗子树
        if(K<=sum)//进入左子树
        {
            for(int i=1;i<=C1;i++)X[i]=LC[X[i]];
            for(int i=1;i<=C2;i++)Y[i]=LC[Y[i]];
            return Query(L,Mid,K);  
        }
        else//进入右子树
        {
            for(int i=1;i<=C1;i++)X[i]=RC[X[i]];
            for(int i=1;i<=C2;i++)Y[i]=RC[Y[i]];
            return Query(Mid+1,R,K-sum);                
        }
    } 
    //外层树状数组
    int Query(int L,int R,int K)
    {
        //预处理需要查询哪log(n)颗主席树 
        C1=C2=0;
        for(int i=(L-1);i;i-=(i&-i))X[++C1]=SegmentTree.Root[i];
        for(int i=R;i;i-=(i&-i))Y[++C2]=SegmentTree.Root[i];
        //"现算现用"查询区间K大 
        return SegTree.Query(1,n,K);
    }
4.3.3两种方法的比较

显然,离散化之后,值和排名是相等的,所以两种方法某种程度上是可以互相交换的.

5.例题

5.1 LG2617 Dynamic Rankings

带修改区间\(K\)小值模板题,按题意操作即可.示例代码采用了记录节点,现算现用的方法.

#include<cstdio>
#include<algorithm>
using namespace std;
#define SIZE 200005 

int n,m;
int nx;
int A[SIZE];//原数组 
//int B[SIZE];//离散化之后的数组
int Tem[SIZE];//离散化临时数组 
int X[SIZE];//计算第[1...L-1]颗主席树的和 需要累加的主席树的编号
int Y[SIZE];//计算第[1...R]颗主席树的和 需要累加的主席树的编号
int C1;//计算第[1...L-1]颗主席树的和 需要累加的主席树的数量
int C2;//计算第[1...R]颗主席树的和 需要累加的主席树的数量

//离散化 
void D()
{
    //for(int i=1;i<=n;i++)Tem[i]=A[i]; 
    sort(Tem+1,Tem+1+nx);
    nx=unique(Tem+1,Tem+1+nx)-(Tem+1);
    //for(int i=1;i<=n;i++)B[i]=lower_bound(Tem+1,Tem+1+nx,A[i])-Tem;
} 

//内层: 动态开点权值线段树
struct SegTreeX
{
    int Tot,Root[SIZE*400],v[SIZE*400],LC[SIZE*400],RC[SIZE*400];
    #define Mid ((L+R)>>1)
    void Change(int &x,int L,int R,int Pos,int Val)
    {
        if(x==0)x=++Tot;
        v[x]+=Val;
        if(L==R)return;
        if(Pos<=Mid)Change(LC[x],L,Mid,Pos,Val);
        else Change(RC[x],Mid+1,R,Pos,Val);
    }
    int Query(int L,int R,int K)
    {
        if(L==R)return L;
        int sum=0;
        for(int i=1;i<=C1;i++)sum-=v[LC[X[i]]];
        for(int i=1;i<=C2;i++)sum+=v[LC[Y[i]]];
        if(K<=sum)
        {
            for(int i=1;i<=C1;i++)X[i]=LC[X[i]];
            for(int i=1;i<=C2;i++)Y[i]=LC[Y[i]];
            return Query(L,Mid,K);  
        }
        else
        {
            for(int i=1;i<=C1;i++)X[i]=RC[X[i]];
            for(int i=1;i<=C2;i++)Y[i]=RC[Y[i]];
            return Query(Mid+1,R,K-sum);                
        }
    } 
}SegTree;

//外层树状数组 
struct BITX
{
    void Change(int Pos,int Val)
    {
        int k=lower_bound(Tem+1,Tem+1+nx,A[Pos])-Tem;//离散化之后的权值 也就是权值线段树里的下标
        for(int i=Pos;i<=n;i+=i&(-i))SegTree.Change(SegTree.Root[i],1,nx,k,Val);
    }
    int Query(int L,int R,int K)
    {
        //预处理需要查询哪log(n)颗主席树 
        C1=C2=0;
        for(int i=(L-1);i;i-=(i&-i))X[++C1]=SegTree.Root[i];
        for(int i=R;i;i-=(i&-i))Y[++C2]=SegTree.Root[i];
        //"现算现用"查询区间K大 
        return SegTree.Query(1,nx,K);
    }
}BIT;

struct qq
{
    int opp,Lx,Rx,k;
}q[SIZE];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){ scanf("%d",&A[i]); Tem[++nx]=A[i]; }
    char op[5];
    for(int i=1;i<=m;i++)
    {
        scanf("%s",op);
        if(op[0]=='Q'){ q[i].opp=1; scanf("%d%d%d",&q[i].Lx,&q[i].Rx,&q[i].k); }
        else { scanf("%d%d",&q[i].Lx,&q[i].k); Tem[++nx]=q[i].k;}
    } 
    D();//离散化
    for(int i=1;i<=n;i++)BIT.Change(i,1);
    for(int i=1;i<=m;i++)
    {
        if(q[i].opp==1)
        {
            printf("%d\n",Tem[BIT.Query(q[i].Lx,q[i].Rx,q[i].k)]);
        }
        else
        {
            BIT.Change(q[i].Lx,-1);
            A[q[i].Lx]=q[i].k;
            BIT.Change(q[i].Lx,1);
        }
    } 
    return 0;
}

5.2 [CQOI2011]动态逆序对

带删除的区间大于/小于\(K\)的值的个数的问题,采用了单独计算,累计答案的方法.

#include<cstdio>
const int SIZE=100005;
int n,m,A[SIZE],F[SIZE];
long long ans;

inline int In()
{
    char ch=getchar();
    int x=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
    return x;
}

char Tem[100];
inline void Out(long long x)
{
    int Len=0;
    while(x){ Tem[++Len]=x%10+'0'; x/=10; }
    while(Len)putchar(Tem[Len--]);
    putchar('\n');
}

//普通树状数组求初始逆序对
struct BIT
{
    long long C[SIZE];
    inline void change(int p,long long v){for(register int i=p;i<=n;i+=i&-i)C[i]+=v;}
    inline long long query(int p){long long x=0;for(register int i=p;i;i-=i&-i)x+=C[i];return x;}   
}T1;

//内层线段树 
struct Segtree
{
    int LC[SIZE*400],RC[SIZE*400],v[SIZE*400],Root[SIZE],Tot;
    void change(int &x,int L,int R,int Pos,int k)
    {
        if(x==0)x=++Tot;
        v[x]+=k;
        if(L==R)return;
        int Mid=(L+R)>>1;
        if(Pos<=Mid)change(LC[x],L,Mid,Pos,k);
        else change(RC[x],Mid+1,R,Pos,k);
    }
    int query(int x,int L,int R,int X,int Y)
    {
        if(x==0||L>Y||R<X)return 0;
        if(L>=X&&R<=Y)return v[x];
        int Mid=(L+R)>>1;
        return query(LC[x],L,Mid,X,Y)+query(RC[x],Mid+1,R,X,Y);
    }
}T2;

//外层树状数组
struct BITx
{
    inline void change(int p,int val,int v){ for(register int i=p;i<=n;i+=i&-i)T2.change(T2.Root[i],1,n,val,v); }
    inline int sum(int p,int Lx,int Rx){ int x=0;for(register int i=p;i;i-=i&-i)x+=T2.query(T2.Root[i],1,n,Lx,Rx);return x;}
    inline int query(int L,int R,int Lx,int Rx)
    {
        if(Lx>Rx||L>R)return 0;
        return sum(R,Lx,Rx)-sum(L-1,Lx,Rx);
    }
}T3;

int main()
{
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n;i++)
    {
        A[i]=In();
        ans+=T1.query(n)-T1.query(A[i]);
        T1.change(A[i],1);
        F[A[i]]=i;
    }
    for(register int i=1;i<=n;i++)T3.change(i,A[i],1);
    for(register int i=1;i<=m;i++)
    {
        Out(ans);
        int x=In();
        ans-=T3.query(1,F[x]-1,x+1,n);//在它前面又比它大
        ans-=T3.query(F[x]+1,n,1,x-1);//在它后面又比它小
        T3.change(F[x],x,-1); 
    }
    return 0;
}

Guess you like

Origin www.cnblogs.com/TaylorSwift13/p/11228276.html