http://poj.org/problem?id=2528
题意:
市长竞选,每个市长都往墙上贴海报,海报之间彼此可以覆盖,给出粘贴顺序和每个海报的起点和终点,问最后又多少海报时可见的。
刚接触离散化,先写一写本人的理解:
如果原数据太大,建树会超内存。因此可以先对这些数排序,然后将它们映射成排序后的序号。比如在该题中,[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、线段树的精髓是,能不往下搜索,就不要往下搜索,尽可能利用子树的根的信息去获取整棵子树的信息。如果在插入线段或检索特征值时,每次都非要搜索到叶子,还不如直接建一棵普通树更来得方便。