A Simple Problem with Integers----線段樹
題目連結:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=66989#problem/C
kuangbin大神的專題,這兩天比賽也多,考試也多,好久都沒寫點東西了,今晚沒有什麼太大的事,正值集訓隊休息,敲篇部落格,看看手生沒生,然後準備準備今晚的codeforces,哎,好晚的說---------||||||||
這個題是裸的區間線段樹更新的題,在講區間線段樹的時候我想應該先講一下單點線段樹的更新,比較好理解。
放一個裸的單點線段樹更新的題:http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=3397
這個是我剛開始學程式碼有一次比賽學長們出的防AK的題,恩,楠神出的,卡了我好久。。。剛開始學你就出線段樹,楠神你也是絕了。。。
我是用靜態方法構建線段樹,即用陣列來做(畢竟下標比較好用),左子樹下標為2×i,右子樹為2×i+1,不要問我為什麼,完全二叉樹的性質,保險起見,陣列一般開4×n,具體為什麼仔細一想就明白了,因為是完全二叉樹,所以第一個葉子節點的下標為大於等於n的第一個2^i,舉例來說n==33,所以第一個葉子節點的下標為64(2^6),所以左孩子為128,右孩子是129,所以是4倍。其實實際用到的是2×n+1個,有一些空間用不到。
有幾個函式在這裡解釋一下是什麼意思PushUp(由下往上更新修改後的資料),Build(初始化線段樹),Update(修改葉子結點的資料),Query(查詢區間資料)
百度圖片裡找了張圖片,很清晰,如果你能夠在寫程式碼的時候腦子裡一直想著樹的形狀會很好寫
跟著程式碼走一邊過程,不用我講你就會單點更新了
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; int a[6000000]; void PushUp(int st) { a[st]=a[st<<1]+a[st<<1|1]; } void Build(int L,int R,int st) { a[st]=0; if(L==R) { scanf("%d",&a[st]); return ; } int mid=(L+R)>>1; Build(L,mid,st<<1); Build(mid+1,R,st<<1|1); PushUp(st); } void Updata(int L,int R,int st,int x,int d) { if(L==R) { a[st]+=d; return ; } int mid=(L+R)>>1; if(x<=mid) Updata(L,mid,st<<1,x,d); else Updata(mid+1,R,st<<1|1,x,d); PushUp(st); } int Query(int L,int R,int st,int l,int r) { if(R<l||L>r) return 0; if(L>=l&&R<=r) return a[st]; int mid=(L+R)>>1; int ans=0; if(l<=mid) ans+=Query(L,mid,st<<1,l,r); if(r>mid) ans+=Query(mid+1,R,st<<1|1,l,r); return ans; } int main() { int n,m; char str[100]; int x,d,l,r; while(~scanf("%d%d",&n,&m)) { Build(1,n,1); while(m--) { scanf("%s",str); if(!strcmp(str,"UPDATE")) { scanf("%d%d",&x,&d); Updata(1,n,1,x,d); } else { scanf("%d%d",&l,&r); printf("%d\n",Query(1,n,1,l,r)); } } cout<<endl; } return 0; }
好吧,我承認上面的程式碼有點囉嗦,不過好入門,入了門以後來看我簡化版的
其實原理是一樣的,看這個題http://acm.hust.edu.cn/vjudge/contest/view.action?cid=66989#problem/A
經典線段樹單點更新
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <stack>
#include <deque>
#define eps 0.00000001
using namespace std;
int a[600000];
int c,b;
void PushUp(int st)
{
while(st--)
a[st]=a[st<<1]+a[st<<1|1];
}
void Updata(int x,int y)
{
while(x)
{
a[x]+=y;
if(a[x]<0)
a[x]=0;
x>>=1;
}
}
int Query(int k,int x,int y)
{
int mid=x+((y-x)>>1),ans=0;
if(c<=x&&b>=y) return a[k];
if(c<=mid) ans+=Query(k<<1,x,mid);
if(mid<b) ans+=Query(k*2+1,mid+1,y);
return ans;
}
int main()
{
int t;
int x,y;
scanf("%d",&t);
int n;
int top=0;
while(t--)
{
memset(a,0,sizeof(a));
top++;
scanf("%d",&n);
int h=1;
while(1)
{
if(h>=n)
break;
h<<=1;
}
int i;
for(i=0;i<n;i++)
{
scanf("%d",&a[h+i]);
}
printf("Case %d:\n",top);
PushUp(h);
char s[1000];
while(1)
{
scanf("%s",s);
if(!strcmp(s,"End"))
break;
else if(!strcmp(s,"Query"))
{
scanf("%d%d",&c,&b);
cout<<Query(1,1,h)<<endl;
}
else if(!strcmp(s,"Add"))
{
scanf("%d%d",&x,&y);
Updata(h+x-1,y);
}
else if(!strcmp(s,"Sub"))
{
scanf("%d%d",&x,&y);
y=-y;
Updata(h+x-1,y);
}
}
}
return 0;
}
這個就簡潔好多了吧,懂了下標後好好利用就行。
這裡我放一個題,就當練練手吧http://acm.hust.edu.cn/vjudge/contest/view.action?cid=66989#problem/B
好了,講了這麼多,單點線段樹算是講完了,我們開看區間更新線段樹。
原理是差不多的,有點不太一樣,每個節點增加一個add變數來記錄增量的累加,因為每一個數都更新到葉子節點是很不明智的選擇,所以,我們找到那個區間,記錄下來。
我記得網上有一張神圖清楚明瞭,但是我找了好久沒找到,當時我也是看到了那張神圖恍然大悟。。。
上程式碼吧
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define eps 0.00000001
using namespace std;
int num[400000];
struct node
{
int l,r;
long long nsum;
long long add;
}seg[400000];
void Build(int i,int l,int r)
{
seg[i].l=l;
seg[i].r=r;
seg[i].add=0;
if(l==r)
{
seg[i].nsum=num[l];
return ;
}
int mid=l+((r-l)>>1);
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
seg[i].nsum=seg[i<<1].nsum+seg[i<<1|1].nsum;
}
void Add(int i,int l,int r,long long c)
{
if(seg[i].l==l&&seg[i].r==r)
{
seg[i].add+=c;
return ;
}
seg[i].nsum+=c*(r-l+1);
int mid=(seg[i].l+seg[i].r)>>1;
if(r<=mid) Add(i<<1,l,r,c);
else if(l>mid) Add(i<<1|1,l,r,c);
else
{
Add(i<<1,l,mid,c);
Add(i<<1|1,mid+1,r,c);
}
}
long long Query(int i,int a,int b)//查詢a-b的總和
{
if(seg[i].l==a&&seg[i].r==b)
{
return seg[i].nsum+(b-a+1)*seg[i].add;
}
seg[i].nsum+=(seg[i].r-seg[i].l+1)*seg[i].add;
int mid=(seg[i].l+seg[i].r)>>1;
Add(i<<1,seg[i].l,mid,seg[i].add);
Add(i<<1|1,mid+1,seg[i].r,seg[i].add);
seg[i].add=0;
if(b<=mid) return Query(i<<1,a,b);
else if(a>mid) return Query(i<<1|1,a,b);
else return Query(i<<1,a,mid)+Query(i<<1|1,mid+1,b);
}
int main()
{
int n,m,i;
int a,b,c;
char s[10];
while(~scanf("%d%d",&n,&m))
{
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
}
Build(1,1,n);
for(i=0;i<m;i++)
{
scanf("%s",s);
if(s[0]=='C')
{
scanf("%d%d%d",&a,&b,&c);
Add(1,a,b,c);
}
else
{
scanf("%d%d",&a,&b);
cout<<Query(1,a,b)<<endl;
}
}
}
return 0;
}
其實線段樹看網上講的再多,不如順著程式碼來一便的快!