1. 程式人生 > 其它 >第一次寫線段樹,以杭電一道題為例 張煊的金箍棒(2)

第一次寫線段樹,以杭電一道題為例 張煊的金箍棒(2)

張煊的金箍棒(2)
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 313 Accepted Submission(s): 125

Problem Description
張煊的金箍棒升級了!

升級後的金箍棒是由幾根相同長度的金屬棒連線而成(最開始都是銅棒,從1到N編號);

張煊作為金箍棒的主人,可以對金箍棒施以任意的變換,每次變換操作就是將一段連續的金屬棒(從X到Y編號)改為銅棒,銀棒或金棒。

金箍棒的總價值計算為N個金屬棒的價值總和。其中,每個銅棒價值為1;每個銀棒價值為2;每個金棒價值為3。

現在,張煊想知道多次執行操作後的金箍棒總價值。

Input
輸入的第一行是測試資料的組數(不超過10個)。

對於每組測試資料,第一行包含一個整數N(1 <= N <= 100000),表示金箍棒有N節金屬組成,第二行包含一個整數Q(0 <= Q <= 100,000),表示執行變換的操作次數。

接下來的Q行,每行包含三個整數X,Y,Z(1 <= X <= Y <= N,1 <= Z <= 3),它定義了一個操作:將從X到Y編號的金屬棒變換為金屬種類Z,其中Z = 1代表銅棒,Z = 2代表銀棒,Z = 3代表金棒。

Output
對於每組測試資料,請輸出一個數字,表示操作後金箍棒的總價值。

每組資料輸出一行。

Sample Input
1
10
2
1 5 2
5 9 3

Sample Output
24

題意:改變一段區間的值,查詢一段區間的值

題解:線段樹的基本應用,更新與查詢區間操作

#include<bits/stdc++.h>                                    
using namespace std;
const int maxn = 1e5 + 5;
typedef long long ll;
ll val[maxn << 2], lazy[maxn << 2
];//開四倍空間存節點 int n, q, x, y, z; void pushup(int rt) { val[rt] = val[rt << 1] + val[rt << 1 | 1]; }//上並,建樹,更新時都要用到 void pushdown(int rt, int l, int r) { if (!lazy[rt]) { return; }//無標記可以直接退出 int mid = (l + r) >> 1; val[rt << 1] = lazy[rt] * (mid - l + 1); val[rt << 1 | 1] = lazy[rt] * (r - mid); lazy[rt << 1] = lazy[rt]; lazy[rt << 1 | 1] = lazy[rt]; lazy[rt] = 0;//標記下推後清零 }//將懶惰標記下推 void build(int l,int r,int rt) {//構建一顆區間[l,r],根節點為rt的線段樹 lazy[rt] = 0; if (l == r) {//找到葉子節點,值先賦好 val[rt] = 1; return; } int mid = (l + r) >> 1; build(l, mid, rt << 1); build(mid + 1, r, rt << 1 | 1); pushup(rt);//向上合併節點,直到根節點 } void update(int a,int b,int v,int l,int r,int rt){ if (a > r || b < l) return;//超出範圍了 if (a <= l && b >= r) {//所求完全包含當前,更新當前點,並做好懶惰標記 lazy[rt] = v; val[rt] = v * (r - l + 1); return; } //若所求區間只是當前區間的一部分,則執行以下程式碼 pushdown(rt, l, r);//下面的遞迴更新會用到下面節點,因此這裡需要下推標記更新 int mid = (l + r) >> 1; update(a, b, v, l, mid, rt<<1); update(a, b, v, mid + 1, r, rt << 1 | 1); pushup(rt);//找到區間並更新完後,向上並區間 } int query(int a,int b,int l,int r,int rt) { if (a > r || b < l)return 0; if (a <= l && b >= r)return val[rt]; int mid = (l + r) >> 1; pushdown(rt, l, r);//同理,只要會用到下面的節點就需要更新 return query(a, b, l, mid, rt << 1) + query(a, b, mid + 1, r, rt << 1 | 1); } int main(){ int t; cin.tie(0); cout.tie(0); ios::sync_with_stdio(false); cin >> t; while (t--) { cin >> n; build(1,n,1); cin >> q; while (q--) { cin >> x >> y >> z; update(x, y,z,1,n,1); } cout << query(1,n,1,n,1) << endl; } }

tips:
1.最好將pushdown和pushup先寫前面,邏輯更清晰
2.建樹,更新,查詢都存在遞迴的過程
3.建樹其實是先找到葉子節點,賦完值然後向上合併,直合併到最後的題目所給區間