HDU4417(主席樹)

前言:

今天剛學的主席樹,附上博客:主席樹
個人覺得這篇文章寫的非常好。

認識:

主席樹就是對於序列1...n 的每一個前綴都構造一顆線段樹來維護所要求的值。也就是說主席樹實際上就是n 棵線段樹。那麼爲什麼不會MLE ?這個下面再說。

先說一個經典的問題:靜態區間第k 大?(求區間[l,r] 之間第k 大的元素。無修改)

  1. 那麼對於序列的前綴,需要知道[1,l1][1,r] 這兩個區間內數的情況。
  2. 在每一個前綴建立的線段樹中,線段樹維護的是[p,q] 這個區間內共有多少個數,注意這裏的p,q 不是我們的初始序列的位置 ,而是初始序列的的值,(一開始看的時候這裏一直搞混,理解這裏的話,主席樹就比較好理解了)。大多數時候數不是連續的,這個時候離散一下就好了。
  3. 這裏就出現了一個很好的性質:每一棵線段樹維護的區間是一樣的,每一個節點都是一樣的(當然他們的節點值肯定不同,不然怎麼玩?)。這個性質就可以保證節點維護的sum 的加減運算不會出問題。
  4. 這個時候查詢[l,r] 之間的第k 小,那麼在序列[1,l1]序列[1,r]這兩棵線段樹之間維護。假設rt[i] 表示[1,i] 的根節點。那麼第一次查詢tree[tree[rt[r]].ls].sumtree[tree[rt[l1]].ls].sum>k 的時候,就在[l,mid] 這棵子樹裏面找,否則就在[mid+1,r] 這棵子樹裏面找。做相應的改變即可。
  5. 這樣時間複雜度很好算o(nlogn)

下面來說一下爲什麼不會MLE ?

  1. 比較兩個前綴[1,i1][1,i] ,發現只是多了一個a[i] 這個數,其他的都沒有變化。
  2. 這個時候如果能夠在[1,i1] 的線段樹上經過小修小補就能完成該有多好啊?
  3. 實際上是可以的,如果這兩個區間維護的是一棵線段樹,那麼這就是一個簡單的區間修改的操作,但是這裏我們還需要保存[1,i1] 的線段樹下來。
  4. 就一個數a[i] ,其實a[i] 對一棵線段樹的多少個區間產生了影響?logn 個區間。這個時候只需要把改變過的logn 的區間重新再新建一個節點,保存新的[1,i] 線段樹就可以了。此時發現絕大多數節點都實現了公用的功能。
  5. 這個時候就會得出一棵主席樹的空間複雜度是o(nlogn) 級別的,因爲兩棵相鄰的線段樹之間絕大多數節點都是公用的。也可以這樣說,每次建立一棵新的線段樹只相當於新增了logn 個節點。

結論

時間複雜度:o(nlogn)
空間複雜度:o(nlogn)

下面貼一下這題的代碼:(模板題)

#include <bits/stdc++.h>

using namespace std;

typedef long long   LL;
typedef vector <int>    VI;
typedef pair <int,int>  PII;
#define FOR(i,x,y)  for(int i = x;i < y;++ i)
#define IFOR(i,x,y) for(int i = x;i > y;-- i)
#define pb  push_back
#define mp  make_pair
#define fi  first
#define se  second

const int maxn = 100010;
int n,m,a[maxn],b[maxn];
map <int,int>   mat;

struct Tree{
    int ls,rs,sum;
}tree[maxn*20];

int rt[maxn],tot;

int build(int l,int r){
    int o = tot++;
    tree[o].sum = 0;
    if(l == r)  return o;
    int mid = (l+r)>>1;
    tree[o].ls = build(l,mid);
    tree[o].rs = build(mid+1,r);
    return o;
}

int update(int x,int l,int r,int lt,int v){
    int o = tot++;
    tree[o] = tree[lt];
    tree[o].sum += v;
    if(l == r)  return o;
    int mid = (l+r)>>1;
    if(x <= mid)    tree[o].ls = update(x,l,mid,tree[lt].ls,v);
    else    tree[o].rs = update(x,mid+1,r,tree[lt].rs,v);
    return o;
}

int query(int i,int j,int x,int l,int r){
    if(l == r)  return tree[i].sum - tree[j].sum;
    int mid = (l+r)>>1,ret = 0;
    if(x <= mid)    ret += query(tree[i].ls,tree[j].ls,x,l,mid);
    else{
        ret += tree[tree[i].ls].sum - tree[tree[j].ls].sum;
        ret += query(tree[i].rs,tree[j].rs,x,mid+1,r);
    }
    return ret;
}

void debug(int o,int l,int r){
    if(l == r)  {printf("%d:%d ",l,tree[o].sum);return;}
    int mid = (l+r) >> 1;
    debug(tree[o].ls,l,mid);
    debug(tree[o].rs,mid+1,r);
}

int main(){
    freopen("test.in","r",stdin);
    int T,tCase = 0;  scanf("%d",&T);
    while(T--){
        printf("Case %d:\n",++tCase);
        scanf("%d%d",&n,&m);
        FOR(i,1,n+1)    scanf("%d",&a[i]),b[i] = a[i];
        sort(b+1,b+n+1);
        int sz = unique(b+1,b+n+1)-b-1;
        FOR(i,1,sz+1)   mat[b[i]] = i;
        tot = 0;
        rt[0] = build(1,sz);
        FOR(i,1,n+1)    rt[i] = update(mat[a[i]],1,sz,rt[i-1],1);
        /*
        FOR(i,1,n+1){
            debug(rt[i],1,sz);   printf("\n");
        }
        */
        FOR(i,0,m){
            int l,r,h;
            scanf("%d%d%d",&l,&r,&h);
            r ++; l ++;
            int k = upper_bound(b+1,b+sz+1,h)-b-1;
            //printf("r:%d l:%d k:%d ",r,l,k);
            if(k)   printf("%d\n",query(rt[r],rt[l-1],k,1,sz));
            else    printf("0\n");
        }
    }
    return 0;
}

發佈了157 篇原創文章 · 獲贊 13 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章