线段树之动态开点  HDU 6183 Color it.

在谈线段树的动态开点之前先说说线段树吧。线段树是用于区间维护,为节省时间而牺牲空间的二叉搜索树,它有几个缺点,先说三个:第一个是区间维护之后是不可逆的,比如我第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\n",ans);
104         //    cout<<ans<<endl;
105         }
106     }
107     return 0;
108 } 

猜你喜欢

转载自www.cnblogs.com/wwq-19990526/p/10561306.html
今日推荐