淺談kdtree


Ⅰ、拋出問題

Description

有一列元素,每一個元素有三個屬性:標號、標識符、數值。這些元素按照標號從1~n排列,標識符也是1~n的一個排列,初始時數值為0。當然我們可以把每個元素看成一個多維數字,那么這列元素就是一個數列。
現在請你維護這個數列,使其能支持以下兩種操作:1.將標號為l~r的所有元素的數值先乘上x,再加上y;2.將標識符為l~r的所有元素的數值先乘上x,再加上y。當然你還得回答某些詢問:1.標號為l~r的所有元素的數值的和;2.標識符為l~r的所有元素的數值的和。

Input

第一行有兩個正整數n、m,分別表示數列長度和操作與詢問個數的總和。第二行有n個正整數,表示每個元素的標識符,保證這n個數是1~n的一個排列。接下來m行,每行的第一個數字為op。若op為0,則表示要進行第一個操作,接下去四個數字表示l,r,x,y;若op為1,則表示要進行第二個操作,接下去四個數字表示l,r,x,y;若op為2,則表示要回答第一個詢問,接下去兩個數字表示l,r;若op為3,則表示要回答第二個詢問,接下去兩個數字表示l,r。

Output

包含若干行,每行表示一個詢問的答案。由於答案可能很大,只要請你輸出答案對536870912取模后的值即可。

Sample Input

4 4
2 1 4 3
0 2 3 4 5
1 1 3 4 7
2 1 1
3 1 1

Sample Output

7
27
HINT
第一次操作后,數列變為0 5 5 0
第二次操作后,數列變為7 27 5 7
N,M<=50000, 1<=L<=R<=N 0<=X,Y<=2^31-1

Source

bzoj4303數列

Ⅱ、分析問題

kdtree,全稱k-dimensional-tree,意思即為k維樹,主要用於解決高維空間的修改查詢操作,支持打標記,求最近最遠點對等,類似於線段樹等數據結構,接下來就來詳細講講kdtree的寫法

1、維護的數據

寫數據結構,一定要弄清維護了哪些數據
kdtree是一種類似於線段樹一樣的數據結構,樹上每一個節點管轄k維區間中的某一個范圍,存每個維度的最大最小值以確定邊界
代碼:
注:代碼中給的是二維kdtree的模板,所以只有兩位

struct hahaha{
    int tp,ls,rs,v[2],Max[2],Min[2],val;//tp為當前節點維護的是哪一維,ls,rs分別為左右兒子編號,v存節點坐標,Max和Min維護當前節點管轄區間的最大最小(即邊界),val存當前點的權值
    int cnt,mlt,sum,len;//cnt加標記,mlt乘標記,sum區間和,len區間長度
    bool operator<(const hahaha &y)const{
        return v[T]<y.v[T];//排序方便尋找中位數
    }
}tree[50010];
2、建樹


如圖,可以看出,kdtree是以一位一位順次分割的方式建樹的,
每次尋找區間中的中位數點,沿當前維度進行分割,如本圖為先豎着再橫着分割
第一次先找到橫向的中位數,豎着分割一次(已用紅色標出),在遞歸左右子樹,找豎着的中位數,橫向分割,再往下依次遞歸
以下建樹部分代碼:

inline void updata(int p){//很顯然的更新
    tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0]));//x坐標的最小值
    tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1]));//y坐標的最小值
    tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0]));//x坐標的最大值
    tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1]));//y坐標的最大值
}
inline int build_tree(int l,int r,int tp){//l,r為區間,tp為區間維度
    T=tp;//T也是區間維度,用於查找中位數
    int mid=((l+r)>>1),p=mid;
    nth_element(tree+l,tree+mid,tree+r+1);//這是個查找中位數的神奇函數
    tree[p].tp=tp;
    tree[p].mlt=1;
    tree[p].len=r-l+1;//更新節點信息
    if(l<mid)
        ls(p)=build_tree(l,mid-1,tp^1);//其實這個地方嚴謹來說應該是(tp+1)%2,因為維度是順次遍歷,假如說有5維,那就是按照0,1,2,3,4,0,1,2...這樣的順序遍歷。
        //注意,這里不是像線段樹一樣l,mid,而是l,mid-1,因為左區間不包括這個點本身
        //之所以這樣寫是為了卡常
    if(r>mid)
        rs(p)=build_tree(mid+1,r,tp^1);
    updata(p);//再次更新節點信息
    return p;
}
3、修改操作

修改操作指的是將在某個范圍內的所有節點的權值更改,支持像線段樹一樣打標記和下傳標記
具體見代碼

//注:本題要求的是先乘上一個數再加上一個數,所以有兩個標記數組
inline void Add_mlt(int p,int v){
    tree[p].val*=v;
    tree[p].cnt*=v;
    tree[p].mlt*=v;
    tree[p].sum*=v;
}
inline void Add_cnt(int p,int v){
    tree[p].val+=v;
    tree[p].cnt+=v;
    tree[p].sum+=tree[p].len*v;
}
inline void pushdown(int p){
    if(tree[p].mlt!=1){//下傳乘標記
        Add_mlt(ls(p),tree[p].mlt);
        Add_mlt(rs(p),tree[p].mlt);
        tree[p].mlt=1;
    }
    if(tree[p].cnt!=0){//下傳加標記
        Add_cnt(ls(p),tree[p].cnt);
        Add_cnt(rs(p),tree[p].cnt);
        tree[p].cnt=0;
    }
}
inline void change(int p,int x,int y,int mt,int ct){//p為當前節點,將第T維(T為全局變量,記錄當前處理維度)坐標在x與y之間的數都乘上mt,加上ct
    if(tree[p].Max[T]<x||y<tree[p].Min[T])//如果不在要處理的范圍內,退出
        return ;
    if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){//如果都在要處理的范圍內,就打上乘標記與加標記
        Add_mlt(p,mt);//加乘標記
        Add_cnt(p,ct);//加加標記
        return ;
    }
    pushdown(p);//下傳標記
    if(x<=tree[p].v[T]&&tree[p].v[T]<=y)//如果當前點在處理范圍內
        tree[p].val=tree[p].val*mt+ct;//處理當前節點
    change(ls(p),x,y,mt,ct);//修改左子樹
    change(rs(p),x,y,mt,ct);//修改右子樹
    tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;//更新當前節點
}
5、查詢操作

詳見注釋

inline int ask(int p,int x,int y){//查詢T維x到y的和
    if(tree[p].Max[T]<x||y<tree[p].Min[T])//出范圍就return 0
        return 0;
    if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y)//在范圍之內就返回和
        return tree[p].sum;
    pushdown(p);//下傳標記
    int res=ask(ls(p),x,y)+ask(rs(p),x,y);//加上左右子樹
    if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
        res+=tree[p].val;//加上這個點本身
    return res;
}

至此,kdtree算法的講解就到此結束,讓我們回到原題
容易想到可以吧原題中的i到j看作一維,吧\(p_i\)\(p_j\)看作第二維,這樣就可以看作是在一個二維平面上進行操作
代碼(模板):

#include<bits/stdc++.h>
#define F(i,j,n) for(register int i=j;i<=n;i++)
#define INF 0x3f3f3f3f
#define ll long long
#define mem(i,j) memset(i,j,sizeof(i))
using namespace std;
#define Md 536870912
int n,m,T;
inline int read(){
    int datta=0;char chchc=getchar();bool okoko=0;
    while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
    while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
    return okoko?-datta:datta;
}
class kd_tree{//之所以用class是為了裝逼
    private:
    public:
    #define ls(p) tree[p].ls
    #define rs(p) tree[p].rs
    int tot,rt;
    struct hahaha{
        int tp,ls,rs,v[2],Max[2],Min[2],val;
        int cnt,mlt,sum,len;
        bool operator<(const hahaha &y)const{
            return v[T]<y.v[T];
        }
    }tree[50010];
    inline void Add(int *a,int v){
        a[0]=a[1]=v;
    }
    inline void updata(int p){
        tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0]));
        tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1]));
        tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0]));
        tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1]));
    }
    inline int build_tree(int l,int r,int tp){
        T=tp;
        int mid=((l+r)>>1),p=mid;
        nth_element(tree+l,tree+mid,tree+r+1);
        tree[p].tp=tp;
        tree[p].mlt=1;
        tree[p].len=r-l+1;
        if(l<mid)
            ls(p)=build_tree(l,mid-1,tp^1);
        if(r>mid)
            rs(p)=build_tree(mid+1,r,tp^1);
        updata(p);
        return p;
    }
    inline void init(){
        Add(tree[0].Max,-INF);
        Add(tree[0].Min,INF);
        F(i,1,n)
            tree[i].v[0]=i,tree[i].v[1]=read();
        rt=build_tree(1,tot=n,0);
    }
    inline void Add_mlt(int p,int v){
        tree[p].val*=v;
        tree[p].cnt*=v;
        tree[p].mlt*=v;
        tree[p].sum*=v;
    }
    inline void Add_cnt(int p,int v){
        tree[p].val+=v;
        tree[p].cnt+=v;
        tree[p].sum+=tree[p].len*v;
    }
    inline void pushdown(int p){
        if(tree[p].mlt!=1){
            Add_mlt(ls(p),tree[p].mlt);
            Add_mlt(rs(p),tree[p].mlt);
            tree[p].mlt=1;
        }
        if(tree[p].cnt!=0){
            Add_cnt(ls(p),tree[p].cnt);
            Add_cnt(rs(p),tree[p].cnt);
            tree[p].cnt=0;
        }
    }
    inline void change(int p,int x,int y,int mt,int ct){
        if(tree[p].Max[T]<x||y<tree[p].Min[T])
            return ;
        if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){
            Add_mlt(p,mt);
            Add_cnt(p,ct);
            return ;
        }
        pushdown(p);
        if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
            tree[p].val=tree[p].val*mt+ct;
        change(ls(p),x,y,mt,ct);
        change(rs(p),x,y,mt,ct);
        tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;
    }
    inline int ask(int p,int x,int y){
        if(tree[p].Max[T]<x||y<tree[p].Min[T])
            return 0;
        if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y)
            return tree[p].sum;
        pushdown(p);
        int res=ask(ls(p),x,y)+ask(rs(p),x,y);
        if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
            res+=tree[p].val;
        return res;
    }
}K;
int main(){
    n=read();m=read();
    K.init();
    F(i,1,m){
        int opt=read(),l=read(),r=read(),x,y;
        if(opt<=1){
            x=read();y=read();
            T=opt;
            K.change(K.rt,l,r,x,y);
        }
        if(opt>=2){
            T=opt-2;
            printf("%d\n",K.ask(K.rt,l,r)&(Md-1));//不這么寫會T
        }
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM