线段树

来源:互联网 发布:周扬青淘宝店 卖什么 编辑:程序博客网 时间:2024/05/21 19:47

初识算法,花了一个下午理解了线段树的算法,然后开始做HDU上的1166题。

先说说对于线段树的一个理解:

 

比如要在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个(多次)询问,每个询问输入一个点,要求这个点在多少条线段上出现过;

最暴力的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中;但是每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n)

假如m是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时

因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费;

线段树就是可以解决这类问题的数据结构。

类似于二叉树,有构造,插入,修改的方法。

关键是要设置一个结构体,来存放不同的线段,然后依次往后构造或者修改。其中关键是左儿子的标识是父亲标识×2,右儿子的标识是父亲标识×2+1.还是用HDU的1166来说明下:

http://acm.hdu.edu.cn/showproblem.php?pid=1166

题目描述:

第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令

要求:

对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数最多不超过1000000。

 

首先我们建立一个结构体:


[cpp] view plaincopyprint?
  1. struct    
  2. {    
  3. int a,b,sum;    
  4. }t[140000];    

其中a,b是线段的坐标,sum是这段线段上key的和;


然后是定义线段数的构造函数:

[html] view plaincopyprint?
  1. int r[50010],SUM; //r[50010]是存放每个点上的人数,SUM是用来存放查询的结果。    
  2. void make(int x,int y,int num)    
  3. {    
  4. t[num].a=x;    
  5. t[num].b=y;    
  6. if(x==y) t[num].sum=r[y];//如果x==y,说明已经是叶子节点了,没有儿子节点了,就显现成熟单个营地,人数就是r[y]    
  7. else{    
  8. make(x,(x+y)/2,num+num); //构造左儿子树    
  9. make((x+y)/2+1,y,num+num+1); //构造右儿子树    
  10. t[num].sum=t[num+num].sum+t[num+num+1].sum; //父节点的人数等于子结点人数之和,线段被分成两段。    
  11. }    
  12. }    

定义查询函数:


[html] view plaincopyprint?
  1. void query(int x,int y,int num)    
  2. {    
  3.     if(x<=t[num].a&&y>=t[num].b)//找到要求的线段区间,返回其值    
  4.         SUM+=t[num].sum;    
  5.     else{    
  6.         int min=(t[num].a+t[num].b)/2;    
  7.         if(x>min) query(x,y,num+num+1);  //要查询的线段在该线段右边,查询该线段的右子节点    
  8.         else if(y<=min) query(x,y,num+num);//要查询的线段在该线段左边,查询该线段的左子节点    
  9.         else{    
  10.             //要查询的线段在该线段中间,分段查询,左右节点都查。    
  11.             query(x,y,num+num);    
  12.             query(x,y,num+num+1);    
  13.         }    
  14.     }    
  15. }    

定义add函数:


[cpp] view plaincopyprint?
  1. void add(int x,int y,int num)    
  2. {    
  3.     //从根节点不断往下更改,只要包含该点x的线段子都增加相应的数量y    
  4.     t[num].sum+=y;    
  5.     if(t[num].a==x&&t[num].b==x) return//找到x的叶子节点。停止。    
  6.     if(x>(t[num].a+t[num].b)/2) add(x,y,num+num+1);//点x在该线段的右边,查询右子节点。    
  7.     else add(x,y,num+num);//否则查询左子节点    
  8. }    

定义sub函数:


[cpp] view plaincopyprint?
  1. view plain  
  2. void sub(int x,int y,int num)    
  3. {    
  4.     t[num].sum-=y;    
  5.     if(t[num].a==x&&t[num].b==x) return;    
  6.     if(x>(t[num].a+t[num].b)/2) sub(x,y,num+num+1);    
  7.     else sub(x,y,num+num);    
  8. }    

下面附带完整代码:

[cpp] view plaincopyprint?
  1. #include<iostream>  
  2. using namespace std;  
  3. struct  
  4. {  
  5.     int a,b,sum;  
  6. }t[140000];  
  7. int r[50010],SUM;  //r[50010]是存放每个点上的人数,SUM是用来存放查询的结果。  
  8. void make(int x,int y,int num)  
  9. {  
  10.     t[num].a=x;  
  11.     t[num].b=y;  
  12.     if(x==y) t[num].sum=r[y];//如果x==y,说明已经是叶子节点了,没有儿子节点了,就显现成熟单个营地,人数就是r[y]  
  13.     else{  
  14.         make(x,(x+y)/2,num+num); //构造左儿子树  
  15.         make((x+y)/2+1,y,num+num+1); //构造右儿子树  
  16.         t[num].sum=t[num+num].sum+t[num+num+1].sum; //父节点的人数等于子结点人数之和,线段被分成两段。  
  17.     }  
  18. }  
  19. void query(int x,int y,int num)  
  20. {  
  21.     if(x<=t[num].a&&y>=t[num].b)//找到要求的线段区间,返回其值  
  22.         SUM+=t[num].sum;  
  23.     else{  
  24.         int min=(t[num].a+t[num].b)/2;  
  25.         if(x>min) query(x,y,num+num+1);  //要查询的线段在该线段右边,查询该线段的右子节点  
  26.         else if(y<=min) query(x,y,num+num);//要查询的线段在该线段左边,查询该线段的左子节点  
  27.         else{  
  28.             //要查询的线段在该线段中间,分段查询,左右节点都查。  
  29.             query(x,y,num+num);  
  30.             query(x,y,num+num+1);  
  31.         }  
  32.     }  
  33. }  
  34. void add(int x,int y,int num)  
  35. {  
  36.     //从根节点不断往下更改,只要包含该点x的线段子都增加相应的数量y  
  37.     t[num].sum+=y;  
  38.     if(t[num].a==x&&t[num].b==x) return//找到x的叶子节点。停止。  
  39.     if(x>(t[num].a+t[num].b)/2) add(x,y,num+num+1);//点x在该线段的右边,查询右子节点。  
  40.     else add(x,y,num+num);//否则查询左子节点  
  41. }  
  42. void sub(int x,int y,int num)  
  43. {  
  44.     t[num].sum-=y;  
  45.     if(t[num].a==x&&t[num].b==x) return;  
  46.     if(x>(t[num].a+t[num].b)/2) sub(x,y,num+num+1);  
  47.     else sub(x,y,num+num);  
  48. }  
  49. int main(int argc, char* argv[])  
  50. {  
  51.     int n,t,i,j;  
  52.     char command[6];  
  53.     cin>>t;  
  54.     j=0;  
  55.     while(t--)  
  56.     {  
  57.         int temp,a,b;  
  58.         cin>>n;  
  59.         r[0]=0;  
  60.         for(i=1;i<=n;i++)  
  61.             cin>>r[i];  
  62.         make(1,n,1);  
  63.         cout<<"Case "<<++j<<":"<<endl;  
  64.         while(cin>>command)  
  65.         {  
  66.             if(strcmp(command,"End")==0)  
  67.                 break;  
  68.             else if(strcmp(command,"Query")==0)  
  69.             {  
  70.                 cin>>a>>b;  
  71.                 SUM=0;  
  72.                 query(a,b,1);  
  73.                 cout<<SUM<<endl;  
  74.             }else if(strcmp(command,"Add")==0)  
  75.             {  
  76.                 cin>>a>>b;  
  77.                 add(a,b,1);  
  78.             }else if(strcmp(command,"Sub")==0)  
  79.             {  
  80.                 cin>>a>>b;  
  81.                 sub(a,b,1);  
  82.             }  
  83.         }  
  84.     }  
  85.     return 0;  
  86. }  
0 0
原创粉丝点击