poj 2528 Mayor's posters(離散化+線段樹)
題意:
市長競選,每個市長都往牆上貼海報,海報之間彼此可以覆蓋,給出貼上順序和每個海報的起點和終點,問最後又多少海報時可見的。
剛接觸離散化,先寫一寫本人的理解:
如果原資料太大,建樹會超記憶體。因此可以先對這些數排序,然後將它們對映成排序後的序號。比如在該題中,[1,4],[2,6],[8,10],[3,4],[7,10]; 先對這些區間端點去重(離散化要注意這一點,可以節省空間)排序後得 1,2,3,4,6,7,8,10 ;那麼可以將這些數對映成其對應編號:
1 2 3 4 6 7 8 10
1 2 3 4 5 6 7 8
那麼原區間就變成[1,4],[2,5],[7,8],[3,4],[6,8]; 這樣建樹只需要 8的空間,按區間端點數值建樹需要10的空間。。如果資料更大,經過離散後會大大節省空間。
思路:
由於wall最大是1000 0000,直接用線段端點座標建樹肯定超記憶體。所以就像上面說的先離散化,再建樹。之後,再從第一張開始貼,每貼一張就去更新相應區間的kind值。最後遞迴統計有多少個不同的kind值即可。
#include <stdio.h> #include <iostream> #include <map> #include <set> #include <stack> #include <vector> #include <math.h> #include <string.h> #include <queue> #include <string> #include <stdlib.h> #include <algorithm> #define LL long long #define _LL __int64 #define eps 1e-12 #define PI acos(-1.0) #define C 240 #define S 20 using namespace std; const int maxn = 100010; int n; int np; struct node { int l,r; int val; }post[10010],tree[80010]; int tmp[100010]; int f[10000010]; //對映函式 int vis[20010]; //用於統計海報數目 int cnt; void build(int v, int l, int r) { tree[v].l = l; tree[v].r = r; tree[v].val = -1; if(l == r) return; int mid = (l+r) >> 1; build(v*2,l,mid); build(v*2+1,mid+1,r); } void update(int v, int l, int r, int val) { if(tree[v].l == l && tree[v].r == r) { tree[v].val = val; return; } if(tree[v].val != -1) { int mid = (tree[v].l + tree[v].r) >> 1; update(v*2,tree[v].l,mid,tree[v].val); update(v*2+1,mid+1,tree[v].r,tree[v].val); tree[v].val = -1; } int mid = (tree[v].l + tree[v].r)>>1; if(r <= mid) update(v*2,l,r,val); else if(l > mid) update(v*2+1,l,r,val); else { update(v*2,l,mid,val); update(v*2+1,mid+1,r,val); } } int query(int v, int l, int r) { if(tree[v].l == l && tree[v].r == r) { if(tree[v].val != -1 && !vis[tree[v].val]) { vis[tree[v].val] = 1; return 1; } else if(tree[v].val == -1) { int mid = (tree[v].l + tree[v].r) >> 1; return query(v*2,tree[v].l,mid) + query(v*2+1,mid+1,tree[v].r); } else return 0; } int mid = (tree[v].l + tree[v].r) >> 1; if(r <= mid) return query(v*2,l,r); else if(l > mid) return query(v*2+1,l,r); else return query(v*2,l,mid) + query(v*2+1,mid+1,r); } int main() { int test; scanf("%d",&test); while(test--) { scanf("%d",&n); np = 0; for(int i = 1; i <= n; i++) { scanf("%d %d",&post[i].l,&post[i].r); tmp[np++] = post[i].l; tmp[np++] = post[i].r; } sort(tmp,tmp+np); //座標離散化並去重 int k = 1; for(int i = 1; i < np; i++) { while(tmp[i] == tmp[k-1]) { i++; if(i == np) break; } if(i == np) break; tmp[k++] = tmp[i]; } np = k; for(int i = 0; i < np; i++) { f[tmp[i]] = i + 1; } build(1,1,np); for(int i = 1; i <= n; i++) { update(1,f[post[i].l],f[post[i].r],i); } memset(vis,0,sizeof(vis)); int ans = query(1,1,np); printf("%d\n",ans); } return 0; }
1、 線段樹是二叉樹,且必定是平衡二叉樹,但不一定是完全二叉樹。
2、 對於區間[a,b],令mid=(a+b)/2,則其左子樹為[a,mid],右子樹為[mid+1,b],當a==b時,該區間為線段樹的葉子,無需繼續往下劃分。
3、 線段樹雖然不是完全二叉樹,但是可以用完全二叉樹的方式去構造並存儲它,只是最後一層可能存在某些葉子與葉子之間出現“空葉子”,這個無需理會,同樣給空葉子按順序編號,在遍歷線段樹時當判斷到a==b時就認為到了葉子,“空葉子”永遠也不會遍歷到。
4、 之所以要用完全二叉樹的方式去儲存線段樹,是為了提高在插入線段和搜尋時的效率。用p*2,p*2+1的索引方式檢索p的左右子樹要比指標快得多。
5、線段樹的精髓是,能不往下搜尋,就不要往下搜尋,儘可能利用子樹的根的資訊去獲取整棵子樹的資訊。如果在插入線段或檢索特徵值時,每次都非要搜尋到葉子,還不如直接建一棵普通樹更來得方便。