1. 程式人生 > >【usaco2017Jan】Promotion Counting的解析——線段樹合併入門

【usaco2017Jan】Promotion Counting的解析——線段樹合併入門

這道題貌似是道水題,看見那麼多人寫樹狀陣列,正好我最近又要學習線段樹合併,於是發了這篇線段樹合併入門blog.

一:線段樹合併的概念.

首先我們先介紹完線段樹合併再來分析題目.

線段樹合併是基於動態開點線段樹的,一開始建立n棵線段樹,每棵線段樹只有一條鏈是記錄著的.

現在我們不斷的兩兩合併這些線段樹,當這兩棵線段樹有共同節點時,合併資訊,其它的直接合並就可以了.

就像這兩棵線段樹就可以這麼合併(節點上的數值表示權值,假設我們現在維護的是區間和,求沒有的點都是0):

那麼這樣子做看上去很暴力,其實時間複雜度和空間複雜度都是比較優秀的,均為O(nlog(n)).

二:複雜度證明.

首先空間複雜度由於合併的時候空間只會減少,而初始每棵樹都是O(log(n))的空間,所以空間是O(nlog(n)).

而時間複雜度最初建樹是O(nlog(n))的,但是隻有而每次不需要進行資訊疊加的節點是不需要耗費複雜度的,進行疊加的節點最多是最初的O(nlog(n))個節點減掉最後的O(n)的節點,算起來也就是O(nlog(n))個節點,而每次疊加也是O(1)的,所以時間複雜度為O(nlog(n)),總的時間複雜度也就是O(nlog(n)).

三:實現線段樹合併.

線段樹合併寫過主席樹的話應該就很簡單了,我們先建立n棵線段樹,每棵線段樹都是一條鏈.

然後每次合併的時候,直接同事遍歷兩棵線段樹,當節點還是重合的時候,我們合併區間資訊,不重合的時候,我們直接把第二棵線段樹上的資訊連結到第一棵上.

這樣還是比較簡單的,具體程式碼看題目分析.

四:解決題目.

題目大意:現在有一棵樹,每個節點i有一個點權v[i],現在我們要求求出每一個節點i為根的子樹中有多少的節點j滿足v[j]>v[i].

那麼這道題其實還是很模板的.

我們先考慮每個節點都建立一棵動態開點權值線段樹,開始只有一條鏈,由於數值範圍很大,我們需要先離散化.

現在我們深度優先遍歷這棵樹,每一次遍歷完一個節點的一個兒子的時候,把這個兒子的線段樹併到當前節點的線段樹中.當遍歷完所有兒子之後,我們直接儲存在這可線段樹中比當前節點權值大的權值數量就可以了.

程式碼實現如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
#define rep(i,j,k) for (int i=j;i<=k;i++)
typedef long long LL;
const int N=100000;
const int INF=(1<<30)-1+(1<<30);
struct tree{
  int ls,rs,l,r,sum;
}tr[N*50];
struct side{
  int y,next;
}e[N*2+9];
int n,p[N+9],lin[N+9],ts,top,root[N+9],order[N+9],cnt[N+9],ans[N+9];
void ins(int X,int Y){
  e[++ts].y=Y;
  e[ts].next=lin[X];
  lin[X]=ts;
}
int find(int k){
  int l,r,mid;
  for (l=1,r=n,mid=l+r>>1;l+1<r;mid=l+r>>1)
    k>order[mid]?l=mid:r=mid;
  return order[l]==k?cnt[l]:cnt[r];
}
void build(int L,int R,int x){
  ++top;
  tr[top].l=L;tr[top].r=R;tr[top].sum=1;
  if (L==R) return;
  int mid=L+R>>1;
  if (x<=mid) tr[top].ls=top+1,build(L,mid,x);
  else tr[top].rs=top+1,build(mid+1,R,x);
}
void merge(int u,int v){
  tr[u].sum+=tr[v].sum;
  if (tr[u].ls&&tr[v].ls) merge(tr[u].ls,tr[v].ls);
  else if (tr[v].ls) tr[u].ls=tr[v].ls;
  if (tr[u].rs&&tr[v].rs) merge(tr[u].rs,tr[v].rs);
  else if (tr[v].rs) tr[u].rs=tr[v].rs;
}
int query(int L,int R,int k){
  if (R<L||!k) return 0;
  if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,tr[k].ls);
  else if (L>mid) return query(L,R,tr[k].rs);
    else return query(L,mid,tr[k].ls)+query(mid+1,R,tr[k].rs);
}
void dfs(int k,int fa){
  for (int i=lin[k];i;i=e[i].next)
    if (fa^e[i].y) { 
      dfs(e[i].y,k);
      merge(root[k],root[e[i].y]);
    }
  ans[k]=query(p[k]+1,n,root[k]);
}
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&p[i]),order[i]=p[i];
  int x;
  for (int i=2;i<=n;i++){
    scanf("%d",&x);
    ins(i,x);ins(x,i);
  }
}
Abigail work(){
  sort(order+1,order+1+n);
  for (int i=1;i<=n;i++)
    cnt[i]=order[i]==order[i-1]?cnt[i-1]:cnt[i-1]+1;
  for (int i=1;i<=n;i++)
    p[i]=find(p[i]);
  for (int i=1;i<=n;i++){
    root[i]=top+1;
    build(1,n,p[i]);
  }
  dfs(1,0);
}
Abigail outo(){
  for (int i=1;i<=n;i++)
    printf("%d\n",ans[i]);
}
int main(){
  into();
  work();
  outo();
  return 0;
}