1. 程式人生 > >淺談zkw線段樹(by Shine_hale)

淺談zkw線段樹(by Shine_hale)

說我 sca can 心理 www. node -- mes 處理

線段樹嘛,很好用的數據結構處理方法但是有個缺點

代碼長,不好理解,但是很強大

其建樹方法是遞歸建樹,調用棧來運行,從上至下,有人說,這類似一個回溯的過程

其實也不然,標記下放後,標記仍需上浮,一上一下,自然速度會很大的降低

那麽有沒有從下而上的操作呢?

zkw神犇出現了,“哈哈,我會”

zkw線段樹從此誕生了,zkw線段樹有很多用途,正在被開發,適用範圍沒有普通線段樹廣,但是其處理單標記問題,是比普通線段樹快一倍以上,甚好甚好

於是蒟蒻的hale查看了各方大佬的博客,以及zkw大佬本人的2013年發表的《統計的力量》雖說我看不懂吧,總算搞懂了一點點

今天給大家講的就是初步的建樹方法,以及區間修改區間求和,嚶嚶嚶

一、

建樹原理請參加《統計的力量》,圖我就不給大家放上來了,我相信各位可以看懂至少他的建樹原理吧

直接貼上代碼了

void push_up(int p)
{ st[p].ans=st[ls(p)].ans+st[rs(p)].ans;}
void build()
{ for (M=1;M<=n+1;M<<=1);
  for (int i=M+1;i<=M+n;i++)
  scanf("%lld",&st[i].ans);
  for (int i=M-1;i;i--)
  push_up(i);
}

二、區間修改

zkw線段樹主要不同於普通線段樹,我認為不是他的非遞歸建樹

而是他的標記永久化以及自底往上的標記上浮原理,這才是他速度快的核心

我不會告訴你我理解這花了一天時間,嚶嚶嚶

首先你要把你的區間做成開區間

nl表示左指針走了多少了

nr表示右指針走了多少了

x表示這層的點的子樹多大

然後大家畫個圖理解一下了

還是很容易的不是嗎

void update(int l,int r,ll k)
{ int s=M+l-1,t=M+r+1,nl=0,nr=0,x=1;
  for (;s^t^1;s>>=1,t>>=1,x<<=1)//這段for包含的信息有點多,還是耐心理解一下最好 
  { st[s].ans+=nl*k;
    st[t].ans
+=nr*k; if (~s&1) {st[s^1].add+=k;st[s^1].ans+=k*x;nl+=x;}//處理左指針,若左指針是左兒子,則右兒子被修改 if (t&1) {st[t^1].add+=k;st[t^1].ans+=k*x;nr+=x;}//處理右指針,若右指針是右兒子,則左兒子北修改 } for (;s;s>>=1,t>>=1)//一加到底,進行修改,防制gg { st[s].ans+=k*nl; st[t].ans+=k*nr; } }

三、區間求和

原理跟更改差不多,就不一一贅述了

直接貼代碼,大家自己多想想就好了

ll query(int l,int r)
{ int s=l+M-1,t=r+M+1,nl=0,nr=0,x=1;
  ll ans=0;
  for (;s^t^1;s>>=1,t>>=1,x<<=1)
  { if (st[s].add) ans+=st[s].add*nl;
    if (st[t].add) ans+=st[t].add*nr;
    if (~s&1) {ans+=st[s^1].ans;nl+=x;}
    if (t&1)  {ans+=st[t^1].ans;nr+=x;}
  }
  for (;s;s>>=1,t>>=1)
  { ans+=st[s].add*nl;
    ans+=st[t].add*nr;
  }
  return ans;
}

四、總結

zkw線段樹,真的好用,快捷,必要時可以考慮一下,很爽的,嚶嚶嚶

其實《統計的力量》當中後面有很多新奇的玩法,奈何hale文化課壓力太大,滾去學文化課了,望各位神犇學會後,教hale一下了

最後的最後就是貼代碼的時間了

本題原型

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int Ma=200010;
int m,n,k,M;
ll a[Ma];
struct node
{ ll ans,add;} st[Ma<<1];
int ls(int p) {return p<<1;}
int rs(int p) {return p<<1|1;}
void push_up(int p)
{ st[p].ans=st[ls(p)].ans+st[rs(p)].ans;}
void build()
{ for (M=1;M<=n+1;M<<=1);
  for (int i=M+1;i<=M+n;i++)
  scanf("%lld",&st[i].ans);
  for (int i=M-1;i;i--)
  push_up(i);
}
void update(int l,int r,ll k)
{ int s=M+l-1,t=M+r+1,nl=0,nr=0,x=1;
  for (;s^t^1;s>>=1,t>>=1,x<<=1)//這段for包含的信息有點多,還是耐心理解一下最好 
  { st[s].ans+=nl*k;
    st[t].ans+=nr*k;
    if (~s&1) {st[s^1].add+=k;st[s^1].ans+=k*x;nl+=x;}//處理左指針,若左指針是左兒子,則右兒子被修改 
    if (t&1)  {st[t^1].add+=k;st[t^1].ans+=k*x;nr+=x;}//處理右指針,若右指針是右兒子,則左兒子北修改 
  }
  for (;s;s>>=1,t>>=1)//一加到底,進行修改,防制gg 
  { st[s].ans+=k*nl;
    st[t].ans+=k*nr;
  }
}
ll query(int l,int r)
{ int s=l+M-1,t=r+M+1,nl=0,nr=0,x=1;
  ll ans=0;
  for (;s^t^1;s>>=1,t>>=1,x<<=1)
  { if (st[s].add) ans+=st[s].add*nl;
    if (st[t].add) ans+=st[t].add*nr;
    if (~s&1) {ans+=st[s^1].ans;nl+=x;}
    if (t&1)  {ans+=st[t^1].ans;nr+=x;}
  }
  for (;s;s>>=1,t>>=1)
  { ans+=st[s].add*nl;
    ans+=st[t].add*nr;
  }
  return ans;
}
int main()
{ int x,y;ll z;
  scanf("%d%d",&n,&m);
  build();
  for (int i=1;i<=m;i++)
  { scanf("%d",&k);
    switch(k)
    { case 1:{scanf("%d%d%lld",&x,&y,&z);
              update(x,y,z);
              break;}
      case 2:{scanf("%d%d",&x,&y);
              printf("%lld\n",query(x,y));
              break;}
    }
  }
  return 0;
}

淺談zkw線段樹(by Shine_hale)