簡單易懂線段樹-第一步建樹-腦袋蒙的來瞧瞧
看了網上好多好多好多的程式碼,能告訴我為啥都用 '>>'這些符號寫的麼...是感覺很好看麼...(確實挺漂亮問題是本來就很蒙的好吧!)還有啊,確實是我實力太差define 定義左右兒子那每次看到函式裡就蒙了,我都替換成正常的了
不過幸好在頭腦清醒的時候弄明白了一些,雖然現在查詢那還是不太懂
好啦好啦不多說了
首先我先把正常有加減乘除符號的程式碼給貼出來,當初我就是卡在這了,幾乎沒見到正常的程式碼= =本來就濛濛的
有這裡卡住的看到就懂了就不用往下看了,用hdu1166舉例子
//hdu 1166 敵兵佈陣
接下來就是我個人看的理解了,有講錯了歡迎指出#include<cstdio> #include<cstring> int sum[55555*4]; void pushup(int rt) { sum[rt] = sum[rt*2]+sum[rt*2+1]; } void build(int l, int r, int rt) { if(l == r) { scanf("%d", &sum[rt]); return ; } int m = (l+r)/2; build(l, m, rt*2); build(m+1, r, rt*2+1); pushup(rt); } void update(int p, int add, int l, int r, int rt) { if(l == r) { sum[rt]+=add; return; } int m = (l+r)/2; if(p <= m)update(p, add, l, m, rt*2); else update(p, add, m+1, r, rt*2+1); pushup(rt); } int query(int L, int R, int l, int r, int rt) { if(L <= l && r <= R)return sum[rt]; int m = (l+r)/2; int ret = 0; if(L <= m) ret += query(L, R, l, m, rt*2); if(R > m)ret += query(L, R, m+1, r, rt*2+1); return ret; } int main() { int tt, n, i, j, k, x, y; scanf("%d", &tt); for(int cas = 1; cas <= tt; ++cas) { printf("Case %d:\n", cas); scanf("%d", &n); build(1, n, 1); char ch[10]; while(scanf("%s", ch),ch[0]-'E') { scanf("%d%d", &x, &y); if(ch[0]=='A')update(x, y, 1, n, 1); else if(ch[0]=='S')update(x, -y, 1, n, 1); else printf("%d\n",query(x, y, 1, n, 1)); } } return 0; }
1、首先既然是線段樹 既然叫做‘樹’ 就說明是樹狀的對吧,這樣我們 怎麼用這樣的樹 來記錄各種值呢
答案就是 用樹葉來記錄每個兵營的人數(我們用這道題舉例子, 大家快去讀一讀是中文題欸)
然後每個節點 就能記錄下面葉子的和,這樣一層一層的記錄就好查了
可是大家還是很蒙,怎麼記錄,怎麼查詢,你光這麼說我也不知道啊,思想誰都會啊!
別急,才開始,且聽我用我的方式慢慢道來
2、開始建立樹,函式 void build(int l, int r, int rt) 我們都需要準備什麼呢? 我這裡不用結構體,就用最直白的
準備: rt, 這個是記錄當前結點標號的,就是每個節點的下標(index)
l, r, 這個是 當前第 rt 節點 所記錄的 左邊 和 右邊, 就是l到r之間的和
在函式最外面還需要一個sum[ ]陣列,用來記錄每個節點記錄的值
接下來是一個關鍵點:當l==r的時候代表什麼呢?
對!當l==r 說明我們當前第rt個點只有記錄了它自己,因為它下面沒有了分支所以他是葉子!!
所以build函式一開始就是這樣
為什麼葉子還用sum記錄人數呢,你想想l==r是不是說明也是個區間和?只不過這個區間只有一個值,所以沒必要開其他陣列混淆視聽啦<pre name="code" class="cpp"><pre name="code" class="cpp">void build(int l, int r, int rt) { if(l == r)//當前發現是葉子,也就是每個兵營 { scanf("%d", &sum[rt]);//那麼我就輸入一個要記錄的兵營人數 return;//當前找到最底部的葉子了,可以返回了 } 函式還沒寫完 }
那麼每次我怎麼找到l == r啊?你去看主函式 ,是不是寫作build(1, n, 1); n是我們兵營數量
也就是說我開始時候l從1開始,r從n開始,1到n就是 rt == 1 記錄的區間
然後函式發現它不是葉子,就繼續執行
void build(int l, int r, int rt)
{
if(l == r)
{
scanf("%d", &sum[rt]);
return;
}
int m = (l+r)/2; //把區間對半分
build(l, m, rt*2);//從l -> m 左半遞迴
build(m+1, r, rt*2+1);//從 m+1 -> r 右半遞迴
pushup(rt);
}
就這樣,不是葉子就對半分,然後左遞迴,右遞迴,記住每次標記rt需要*2和*2+1,這是二叉樹的規律嘛,我們利用了這個規律
pushup用來每次遞迴已經確立的左子樹和右子樹的和
void pushup(int rt)
{
sum[rt] = sum[rt*2]+sum[rt*2+1];
}
到這裡建樹就基本完成了,我實力太差只能寫成這樣了,我的想法就是理解如何寫出的程式碼,然後用理解的方式背下來,
至少能記住很久,至於真正理解這樣的思想就靠題和時間 還有 之後的返工了,我這裡只是講了怎麼理解寫出來的
看看有木有人看,有木有人理解,要是需要我再寫新增add和查詢query,其實這倆和建樹異曲同工啦我也想成為厲害的人,給自己個交代我也是能做到很厲害的人,否則一直會籠罩在沒有自信之中
大家一起努力啦啦啦
最後提示一下如果用cin和cout一定會超時的,我試著用
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
剩下的主函式接著寫
}
我試著用這兩句話加速cin和cout 才勉強980多ms踩過,而且解除和scanf的繫結之後一開始輸出Case什麼的會報錯
還需要拆開輸出,很費勁,就算我不用endl也才省了20多ms(之前看貼吧有人說endl費時),所以老老實實用scanf和printf吧!