• 高级数据结构—线段树(一)


    线段树的原因是因为cf的一道题目始终想不出来怎么优化,后来知道区间查询和修改要用到线段树。。。

    原题:Iva & Pav

    线段树的作用

    1. 区间最值查询:可以高效地找到给定区间内的最大值、最小值等。

    2. 区间和查询:可以高效地计算给定区间内元素的和、积等。

    3. 区间更新:可以高效地对给定区间内的元素进行更新操作,如增加一个固定值、赋值等。

    4. 区间覆盖:可以将给定区间内的元素全部赋值为一个固定值。

    5. 区间合并:可以将多个区间合并成一个区间,快速地进行区间合并操作。

    6. 区间离散化:可以将区间内的元素进行离散化处理,方便进行查询和统计操作。

    7. 区间交集:可以快速地找到多个区间之间的交集

    线段树和树状数组的区别 

     刚学完树状数组来学线段树,一开始还不知道他们具体的差别在哪里,那么以下是我的理解。

    1.树状数组是前缀和优化,要用到前缀和的时候较为方便。

    2.树状数组用来进行单点修改,区间查询;或者区间修改,单点查询较为方便,而区间查询和区间修改较为复杂,因此可以用线段树优化。

    3.线段树适用于需要频繁的区间查询和更新操作的问题,如区间最值、区间和等,能够灵活处理各种区间操作。

    4.树状数组适用于一维数组的前缀和查询和更新操作,对于简单的区间操作也能够提供高效的解决方案。

    例题: 

    最大数

    题目链接:最大数

    直接看代码:

    1. #include
    2. #define int long long
    3. using namespace std;
    4. //单点插入,区间查询
    5. const int N = 2e5+5;
    6. struct node{
    7. int l,r;
    8. int v;
    9. }tr[N*4];
    10. int m,p;
    11. //子节点的信息更新父节点
    12. void pushup(int u){
    13. tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
    14. }
    15. //u为当前线段树节点编号
    16. void build(int u,int l,int r){
    17. tr[u]={l,r};
    18. if(l==r)return;
    19. int mid=l+r>>1;
    20. build(u<<1,l,mid);
    21. build(u<<1|1,mid+1,r);
    22. }
    23. //查询以u为根节点,区间[l,r]中的最大值
    24. int query(int u, int l, int r) {
    25. // Tl-----Tr
    26. // L-------------R
    27. //1.不必分治,直接返回
    28. if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    29. int mid = tr[u].l + tr[u].r >> 1;
    30. int v = 0;
    31. // Tl----m----Tr
    32. // L-------------R
    33. //2.需要在tr的左区间[Tl, m]继续分治
    34. if(l <= mid) v = query(u << 1, l, r);
    35. // Tl----m----Tr
    36. // L---------R
    37. //3.需要在tr的右区间(m, Tr]继续分治
    38. if(r > mid) v = max(v, query(u << 1 | 1, l, r));
    39. // Tl----m----Tr
    40. // L-----R
    41. //2.3涵盖了这种情况
    42. return v;
    43. }
    44. //u为节点编号,x为修改位置,v为修改的值
    45. void modify(int u,int x,int v){
    46. if(tr[u].l==tr[u].r)tr[u].v=v;//叶子节点,递归出口
    47. else{
    48. int mid=tr[u].l+tr[u].r>>1;
    49. //分治,修改位置偏左往左边遍历,偏右往右边遍历
    50. if(x<=mid)modify(u<<1,x,v);
    51. else {
    52. modify(u<<1|1,x,v);
    53. }
    54. pushup(u);//回溯,子节点的信息更新父节点
    55. }
    56. }
    57. signed main(){
    58. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    59. //n表示树中节点个数,last表示上一次查询的结果
    60. int n=0,last=0;
    61. cin>>m>>p;
    62. //初始化线段树,节点的区间最多为[1,m]
    63. build(1,1,m);
    64. while(m--){
    65. char op;cin>>op;
    66. if(op=='A'){//添加节点
    67. int t;cin>>t;
    68. //在n+1处插入
    69. modify(1,n+1,(t+last)%p);
    70. //节点个数+1
    71. n++;
    72. }
    73. else {
    74. int l;cin>>l;
    75. //查询[n - L + 1, n]内的最大值,u = 1,即从根节点开始查询
    76. last=query(1,n-l+1,n);
    77. cout<"\n";
    78. }
    79. }
    80. return 0;
    81. }

    你能回答这些问题吗

    题目链接:你能回答这些问题吗

    如图:

    如图,假设我们要求区间的最大子段和,有三种情况:

    1.包含所有左半边,部分右半边----->左半边的区间和+右半边的前缀和

    2.包含所有右半边,部分左半边----->右半边的区间和+左半边的后缀和

    3.中间的一部分----->左半边的后缀和+右半边的前缀和

    因此我们的结构体要记录四个信息:

    1. struct node{
    2. int l,r;
    3. int sum;//[l,r]的区间和
    4. int lmax;//最大前缀和
    5. int rmax;//最大后缀和
    6. int tmax;//区间[l,r]最大连续子段和
    7. }tr[N*4];

     同时pushup函数根据上图可以推出:(重载函数)

    1. //u表示该节点,l表示该节点的左子树,r表示该节点的右子树
    2. void pushup(node &u,node &l,node &r){
    3. u.sum=l.sum+r.sum;
    4. //三种最大连续子段和的情况
    5. u.lmax=max(l.lmax,l.sum+r.lmax);
    6. u.rmax=max(r.rmax,r.sum+l.rmax);
    7. u.tmax=max({l.tmax,r.tmax,l.rmax+r.lmax});
    8. }
    9. void pushup(int u){
    10. pushup(tr[u],tr[u<<1],tr[u<<1|1]);
    11. }

    代码附上:

    1. #include
    2. #define int long long
    3. using namespace std;
    4. const int N = 5e5+5;
    5. int w[N];
    6. int n,m;
    7. struct node{
    8. int l,r;
    9. int sum;//[l,r]的区间和
    10. int lmax;//最大前缀和
    11. int rmax;//最大后缀和
    12. int tmax;//区间[l,r]最大连续子段和
    13. }tr[N*4];
    14. //u表示该节点,l表示该节点的左子树,r表示该节点的右子树
    15. void pushup(node &u,node &l,node &r){
    16. u.sum=l.sum+r.sum;
    17. //三种最大连续子段和的情况
    18. u.lmax=max(l.lmax,l.sum+r.lmax);
    19. u.rmax=max(r.rmax,r.sum+l.rmax);
    20. u.tmax=max({l.tmax,r.tmax,l.rmax+r.lmax});
    21. }
    22. void pushup(int u){
    23. pushup(tr[u],tr[u<<1],tr[u<<1|1]);
    24. }
    25. void build(int u,int l,int r){
    26. if(l==r)tr[u]={l,r,w[r],w[r],w[r],w[r]};//找到叶子节点
    27. else{
    28. tr[u]={l,r};//设当前区间为[l,r]
    29. int mid=l+r>>1;
    30. build(u<<1,l,mid);//左子树
    31. build(u<<1|1,mid+1,r);//右子树
    32. pushup(u);//修改父节点
    33. }
    34. }
    35. //每次从1号节点开始找,找到位置位于x的数,并把它修改为v
    36. void modify(int u,int x,int v){
    37. if(tr[u].l==x && tr[u].r==x)tr[u]={x,x,v,v,v,v};
    38. else{
    39. int mid=tr[u].l+tr[u].r>>1;
    40. if(x<=mid)modify(u<<1,x,v);//x位于当前区间的左半子区间
    41. else modify(u<<1|1,x,v);//x位于当前区间的右半子区间
    42. pushup(u);//修改父节点的相关信息
    43. }
    44. }
    45. node query(int u,int l,int r){
    46. if(tr[u].l>=l&&tr[u].r<=r)return tr[u];//被包含
    47. else{
    48. int mid=tr[u].l+tr[u].r>>1;
    49. if(r<=mid)return query(u<<1,l,r);//查询左半区间
    50. else if(l>mid)return query(u<<1|1,l,r);//查询右半区间
    51. else{//横跨左右区间
    52. auto left=query(u<<1,l,r);
    53. auto right=query(u<<1|1,l,r);
    54. node res;
    55. pushup(res,left,right);
    56. return res;
    57. }
    58. }
    59. }
    60. signed main(){
    61. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    62. cin>>n>>m;
    63. for(int i=1;i<=n;i++)cin>>w[i];
    64. build(1,1,n);//建树
    65. int k,x,y;
    66. while(m--){
    67. cin>>k>>x>>y;
    68. if(k==1){//查询
    69. if(x>y)swap(x,y);
    70. cout<<query(1,x,y).tmax<<"\n";
    71. }
    72. else modify(1,x,y);//修改
    73. }
    74. return 0;
    75. }

  • 相关阅读:
    java io流
    OPENWRT本地局域网模拟域名多IP
    互联网摸鱼日报(2022-12-07)
    1.2 向量的长度与点积
    小程序 + dayjs自定义 picker 时间选择器
    【MySQL】20-MySQL如何创建和管理表超详细汇总
    ECCV 2022 | OA-MIL:目标感知多实例学习方法
    Kubernetes 单节点安装Clickhouse
    [网络] TCP协议是什么?套接字Socket是什么?它们是什么关系?
    win10-wsl安装卸载以及软件安装
  • 原文地址:https://blog.csdn.net/2302_81761369/article/details/138123888