在谈线段树的动态开点之前先说说线段树吧。线段树是用于区间维护,为节省时间而牺牲空间的二叉搜索树,它有几个缺点,先说三个:第一个是区间维护之后是不可逆的,比如我第7次修改了区间内容后,返回想看一下我在第5次修改之前的数据就看不鸟了,好可恶~;第二个是空间牺牲太大,虽然说时间和空间不可得兼,但是维护一个区间,需要消耗4倍区间的内存还是有点多了,而且很多时候开出来的点还没有被用到,浪费了空间,好可恶~;第三个是第二个问题衍生出来的,因为开的空间很大,一道题基本上建几棵树就把内存用完了,可恶可恶~,我要森林!于是,算法进阶一下:节省空间——线段树的动态开点!
什么是线段树的动态开点?借用前辈们的一句话便是:开局一个根,装备(枝叶)全靠给。详细用下面这道题来解释吧。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6183
题目大意:在1-1e6的矩形坐标上进行有4个操作可以进行:操作0(输入0)清空坐标系;操作1(输入1,x,y,c)在(x,y)上添加颜色c ;操作2(输入2,x,y1,y2).查询(1,y1),(x,y2)构成的矩形区域内的颜色种类数量;操作3(输入3)退出。
数据大小:1≤x,y≤1e6,0≤c≤50,操作数量为150000,清空次数不会超过10次。
解题思路:当只有一种颜色时,区间搜索可以用线段树来写:因为x轴每次都是从1开始,而y轴是不断在变化的区间,用线段树来维护y轴,树中存入一段区间内上色的最小x坐标,所以当给定一段y轴区间和一段x区间(1-m)的区间时,只要查询到对应的y区间内m>x即可以说明该面积内有上色。但是此题有50种颜色,则需要建立50棵树去维护,看数据大小,显然是不可行的。
线段树动态开点比动态树少了建树一个模块,因为动态开点不需要直接建树,而是在更新部分建树,另外,动态开点是从上往下开,首先是将整个大区间建一个点,然后更新哪个点就往哪个方向建点,这里注意的是这些点不是像二叉树一样每个角标是有规律的,因为边开边建,所以先开的点在一起,比如可能根节点是Node[1],右儿子为Node[2],右儿子的右儿子为Node[3],再建左边则左儿子为Node[4]...所以说开的点为事先定义的数组Node;而把他们构成树是用Node中的lson和rson,比如Node[1].rson=2,Node[1].lson=4;表示Node[1]点的右子树和左子树为点2和点4。这样就很好满足了用几个点就开几个点的条件了。接下来可以根据下面ac代码来看看,自己觉得注释的蛮细的(数据太多,用c输入输出,c++超时)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 const int SIZE=1000000+5; 6 int INF=SIZE+5; 7 int num; //num用于开点用,为开点的角标 8 int root[55]; //树的根结点。 9 bool key; //key用于判断区域内是否涂色,true是false否。 10 struct Node{ 11 int l,r,lson,rson; //l,r节点的左右区间和;lson,rson左右儿子节点角标 12 int minx; //minx即该区间涂色点的最小x坐标 13 }Node[SIZE<<2]; 14 int min(int a,int b) 15 { 16 return a>b?b:a; 17 } 18 19 void push(int n,int l,int r,int x) //更新角标为n的点信息 20 { 21 Node[n].l=l; 22 Node[n].r=r; 23 Node[n].minx=min(Node[n].minx,x); 24 return; 25 } 26 27 void update(int &rt,int l,int r,int x,int y) 28 { 29 if(!rt) rt=++num; //如果这个点不存在,就开一个新点,如果存在,就更新存在的那一点。 30 //rt取的地址有两种情况,一是根节点root[i],另一个就是Node[i]的左右儿子 31 push(rt,l,r,x); //更新这个点的信息 32 if(l==r) return; 33 //{ cout<<"<---"<<l<<"---"<<r<<"--->"<<endl;return; } //更新到叶子节点结束 34 int mid=(l+r)>>1; 35 if(mid>=y) //只开包含y的一边 36 update(Node[rt].lson,l,mid,x,y); //开左子树 37 else 38 update(Node[rt].rson,mid+1,r,x,y); //开右子树 39 } 40 void query(int rt,int l,int r,int x) 41 { 42 if(key||!rt) return; //如果rt点没有被开发或者已经找到就直接退出 43 //cout<<"!!!"<<rt<<" ##"<<Node[rt].l<<" ##"<<Node[rt].r<<"!!!"<<endl; 44 if(Node[rt].l>=l&&Node[rt].r<=r) 45 { 46 if(Node[rt].minx<=x) //如果最小的minx不超过x,则说明1-minx在1-x里面,即1-x内有涂色 47 key=true; 48 return; 49 } 50 int mid=(Node[rt].l+Node[rt].r)>>1; 51 if(mid>=l) 52 query(Node[rt].lson,l,r,x); 53 if(r>mid) 54 query(Node[rt].rson,l,r,x); 55 return; 56 } 57 void init() //初始化函数 58 { 59 num=0; //开的点从0开始开点 ,所以一开点角标就是非0了 60 memset(root,0,sizeof(root)); //根节点都赋值0 61 for(int i=0;i<(SIZE<<2);i++) 62 { 63 Node[i].rson=Node[i].lson=0; //每个点都未用,左右儿子都为0 64 Node[i].minx=INF; //每个点Node点代表一个区间,未用该点,该区间minx不存在,即赋值INF 65 } 66 return; 67 } 68 int main() 69 { 70 int op,a,b,c; //op为操作operate 0 1 2 3 71 init(); 72 while(~scanf("%d",&op)/*cin>>op*/&&op-3) 73 { 74 if(op==0) 75 init(); 76 else if(op==1) 77 { 78 scanf("%d %d %d",&a,&b,&c); 79 //cin>>a>>b>>c; 80 update(root[c],1,1000000,a,b); 81 // cout<<"-----------------------------------------"<<endl; 82 // cout<<"目前有多少个节点:"<<num<<endl; 83 // for(int i=0;i<=num;i++) 84 // cout<<i<<">> l:"<<Node[i].l<<" r:"<<Node[i].r<<" lson:"<<Node[i].lson<<" rson:"<<Node[i].rson<<" minx:"<<Node[i].minx<<endl; 85 // cout<<"-----------------------------------------"<<endl; 86 // for(int i=0;i<=50;i++) 87 // cout<<root[i]<<" "; 88 // cout<<endl; 89 // cout<<"-----------------------------------------"<<endl; 90 } 91 else if(op==2) 92 { 93 scanf("%d %d %d",&a,&b,&c); 94 //cin>>a>>b>>c; 95 int ans=0; 96 for(int i=0;i<=50;i++) //遍历50棵线段树 97 { 98 key=false; 99 query(root[i],b,c,a); //访问第i棵树区间b,c内1到a有没有第i种颜色 100 if(key) 101 ans++; 102 } 103 printf("%d ",ans); 104 // cout<<ans<<endl; 105 } 106 } 107 return 0; 108 }