2016.5.10 神奇的博主回來了
今天,當然講的就是加強版的堆,其實算線段樹的說。。。
(還有你們看過的留留言好不好呀,謝謝了)
---------------------------
今天說的就是[[[加強版!!!]]]
內容大致就是成段更新,區間求和
也就是說,會用到高大上的lazy tag,那麼lazy tag有多lazy呢?神奇的是,如果每次區間全都加一個數,然後每次全都從下面到頂走一遍的話,妥妥的就炸。所以說,利用lazy tag標記的話,其實就是如果有增加的話,只是做一個標籤,在有必要的時候再調整,可以大幅縮小運行需要。
因爲需要,博主選擇一段一段的來寫,當然會在結尾貼上完整代碼。(所謂的最後一排/最頂上的,看不懂的話,請在上面的博文中找圖)
-----------------------------
因爲是一段一段的,就不加註釋符了。
首先,是建樹
struct Node
{
int l,r;
l所表示的是左子的管轄範圍就是說能管的區間的最左端
r所表示的是右子的管轄範圍就是說能管的區間的最右端
int sum2,lazy;
sum也就是左子右子所管轄的sum的和
lazy也就是lazy tag
爲了便於下面的閱讀,我將在這裏簡單介紹一下<<&>>
首先,<<代表的是二進制移位(往左移位) >>代表的是二進制 往右移位,後面跟的數字就是移位的數。那移位又能幹什麼呢?比如:
2的二進制是00010是吧
移位一位 00100就是4
移位兩位 01000就是8
移位三位 10000就是16
可以看出往左移位1位是乘2,2位是乘4,3位呢,就是乘8等等,說是用起來比乘法要快一些。
反之就是一次除二
} tree[N<<2]; 不是乘2,是乘4!理論上乘3就夠了,但是多一點總沒壞處,只要不炸就行。
下面就是建樹了。
我是瀟灑哥,瀟灑到根本不處理原數,直接用sum。其實最下面的sum就是原數所以其實更便捷了。
void build(int l,int r,int rt)
這個建樹時使用的是遞歸建樹。
{
tree[rt].l = l;
tree[rt].r = r;
完整程序中是build(1,n,1); 當然,不可能全都管轄範圍最左端全是1,右端也不可能全是n,這就是爲什麼遞歸,當l和r都是初始的1和n時,那麼我們在處理的一定是頂端第一個,那麼頂端第一個的左端右端就是1和n。
tree[rt].lazy=0;
lazy置空,因爲還沒有涉及到成段更新的問題,初始化一下
if(l == r)
{
如果說左端和右端一樣了,那麼就說明是最下面一排的了,那麼就要開始輸入處理了。
scanf("%d",&sum[rt]);
輸入sum,因爲最後一排的管轄總和就是自己本身的數
return ;
}
build(lson);
遞歸左子,你一定會想,三個參數爲啥就一個了。。。這裏容我解釋一下,前面我用了宏
(#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1)
當然,前面<<和>>也做了相應解釋,在這裏與或非中的或(|)爲什麼代表+1,我也不是很懂,所以如果有大神指點一下,謝謝。
乘2是左子,乘2+1是右子。涉及到位運算的相關知識,如果說是1|1,那麼肯定答案還是1。但是它前面帶着一個左移,那麼最後一位一定是0,那麼或1一定是+1。附上相關知識鏈接:http://baike.baidu.com/link?url=8gk3GFWZb0DQexRjbrdMjcRkept4HNaaR1EJ4HvMbgLm5COvKWmXPDoNtZFFayzKcS1mR1YnoKX_HHNd9iVTDq
build(rson);
PushUp(rt);
這個PushUp的作用,接下來揭曉。
}
-----------------------------
下面我會選擇先把易懂的查詢寫完。
int query(int l,int r,int rt)
還是遞歸,我似乎太喜歡遞歸了吧。。。
{
if(l == tree[rt].l && r == tree[rt].r)
如果說正好的話
{
return tree[rt].sum2;
那答案就是當前的和
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
這個PushDown的作用,接下來揭曉。
int res = 0;
這個res就是最後的答案。
int m=(tree[rt].l+tree[rt].r)/2;
m所代表的就是一個分界線
就是說,比如說從3到5查詢
一共5個數
最開始的一定是1,所以左子是1,右子是5
m=(1+5)/2=3
可以瞭解一下,也很有趣,也是從中間平均分)差不多,從中間分開一下。
if(r <= m) res += query(l,r,rt<<1);
如果說右子還在m的左面,也就說所有的全部都在左邊的話,那就處理左子
else if(l > m) res += query(l,r,rt<<1|1);
如果說左子還在m的右面,也就說說有的全部都在右面的話,那就處理右子
else
{
res += query(l,m,rt<<1);
res += query(m+1,r,rt<<1|1);
有左有右的話,就求兩邊的和
}
return res;
返回結果
}
-----------------------------
因爲在build裏面先出現的是PushUp,所以也沒什麼理由,任性決定先寫PushUp。
void PushUp(int rt)
數據上推,也就是說(找圖看)上面的數據
{
tree[rt].sum2=tree[2*rt].sum2+tree[2*rt+1].sum2;
當前的sum就是左子右子sum的和,和上面博文中的max差不太多
}
-----------------------------
void PushDown(int rt,int m)
下發數據,m是左右子區間長度
{
if(tree[rt].lazy==0) return;
如果說沒有要加的lazy那就省事了
tree[rt*2].lazy+=tree[rt].lazy;
有的話,下發給左子
tree[rt*2+1].lazy+=tree[rt].lazy;
下發給右子
tree[rt*2].sum2+=tree[rt].lazy*(m-(m/2));
它左子加的和,一定是它能管到的數量的一半再乘上lazy
因爲二叉樹左子管到的一定是一半,而每個都是加lazy
但是和左子不同的是,雖然說在數學的角度,(m-(m/2))和(m/2)似乎是一樣的,但是說如果是單數這樣的話,就會有誤差,當然誤差會像滾雪球一樣越滾越大,所以說要特殊處理一下。
tree[rt*2+1].sum2+=tree[rt].lazy*(m/2);
右子不同。
tree[rt].lazy=0;
必須清空!!!看似微小的錯誤往往是致命的!如果鑰匙沒有置空的話,就會一直循環下去!!!一定要注意啊!
}
-----------------------------
還剩一個update
void update(int c,int l,int r,int rt)
{
又是遞歸。。。big boss來啦,就是所謂的成段更新
首先解決三個參數,第一個就是更新所加的數值,第二個第三個就是成段更新的範圍,第4個rt當然就是當前位置不用多說
if(tree[rt].l == l && r == tree[rt].r)
如果位置正好
{
tree[rt].lazy+=c;
當前位置的lazy直接就是更新所加的數
tree[rt].sum2+=(int)c * (r-l+1);
當然這個跟上面PushDown中的一模一樣,那爲什麼還要加呢?
當然這個問題愚蠢的樓主想了一會,本來就要改代碼了,發現其實如果在這種情況下根本不會調用PushDown,所以並不存在重複的情況,可以安心使用。
return;
遞歸須謹慎啊!debug傷不起。。。
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
記得處理完把數據下發一下。
int m=(tree[rt].l+tree[rt].r)/2;
m還是分界
if(r <= m) update(c,l,r,rt<<1);
其實這一段就跟query沒什麼區別啦,只不過是更新而已,判斷條件都沒變呢,還是自己去看吧
else if(l > m) update(c,l,r,rt<<1|1);
else
{
update(c,l,m,rt<<1);
update(c,m+1,r,rt<<1|1);
}
PushUp(rt);
上推一下數據。
}
-----------------------------
主函數非常簡單,在這裏也不再贅述,當然,前面所答應的完整代碼是一定會放的,首先,我找到了一道poj3468,先分析一下題目:
A Simple Problem with Integers
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 89915 Accepted: 27990
Case Time Limit: 2000MS
Description
You have N integers, A1,A2, ... ,AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1,A2, ... ,AN. -1000000000 ≤Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa,Aa+1, ... ,Ab. -10000 ≤c ≤ 10000.
"Q a b" means querying the sum of Aa,Aa+1, ... ,Ab.
Output
You need to answer all Q commands in order. One answer in a line.
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
Hint
The sums may exceed the range of 32-bit integers.
Source
POJ Monthly--2007.11.25, Yang Yi
這道題所講的就是成段更新,區間求和。Q是區間查詢,求和。C是成段更新。值得注意的是,“The sums may exceed the range of 32-bit integers.”,用int要炸,但是poj的編譯系統只認__int 64,Xcode中的lld似乎是不行的。
代碼:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100005;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
struct Node
{
int l,r;
__int64 sum,lazy;
int mid()
{
return (l+r)>>1;
}
} tree[N<<2];
void PushUp(int rt)
{
tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;
}
void PushDown(int rt,int m)
{
if(tree[rt].lazy)
{
tree[rt*2].lazy+=tree[rt].lazy;
tree[rt*2+1].lazy+=tree[rt].lazy;
tree[rt*2].sum+=tree[rt].lazy*(m - (m>>1));
tree[rt*2+1].sum+=tree[rt].lazy*(m>>1);
tree[rt].lazy=0;
}
}
void build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
tree[rt].lazy=0;
if(l == r)
{
scanf("%I64d",&tree[rt].sum);
return ;
}
int m=tree[rt].mid();
build(lson);
build(rson);
PushUp(rt);
}
void update(int c,int l,int r,int rt)
{
if(tree[rt].l == l && r == tree[rt].r)
{
tree[rt].lazy+=c;
tree[rt].sum+=(int)c * (r-l+1);
return;
}
if(tree[rt].l == tree[rt].r) return;
PushDown(rt,tree[rt].r - tree[rt].l + 1);
int m = tree[rt].mid();
if(r <= m) update(c,l,r,rt<<1);
else if(l > m) update(c,l,r,rt<<1|1);
else
{
update(c,l,m,rt<<1);
update(c,m+1,r,rt<<1|1);
}
PushUp(rt);
}
__int64 query(int l,int r,int rt)
{
if(l == tree[rt].l && r == tree[rt].r)
{
return tree[rt].sum;
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
int m=tree[rt].mid();
__int64 res = 0;
if(r <= m) res += query(l,r,rt<<1);
else if(l > m) res += query(l,r,rt<<1|1);
else
{
res += query(l,m,rt<<1);
res += query(m+1,r,rt<<1|1);
}
return res;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
//按位取反
{
build(1,n,1);
while(m--)
{
char ch[2];
scanf("%s",ch);
int a,b,c;
if(ch[0] == 'Q')
{
scanf("%d %d", &a,&b);
printf("%I64d\n",query(a,b,1));
}
else
{
scanf("%d %d %d",&a,&b,&c);
update(c,a,b,1);
}
}
}
return 0;
}
-----------------------------
再給你們點福利,兩組測試數據奉上:
10 4
1 5 8 2 5 9 3 5 8 9
Q 3 6
C 4 6 1
Q 5 8
Q 4 5
24
24
9
==================
29 5
2 5 2 3 6 7 3 5 8 3 7 5 4 1 2 6 8 3 6 3 2 5 7 9 8 6 3 1 5
Q 3 19
C 1 28 5
Q 3 6
C 3 6 8
Q 5 10
79
38
78
—————————————————————————————就這樣的,然後不懂的可以留言,還有一道線段樹poj3667也很適合,會在稍後奉上。