zoukankan      html  css  js  c++  java
  • HDU 4893 Wow! Such Sequence!(2014年多校联合 第三场 G)(线段树)

    磨了一天的线段树,不能说完全搞清楚,只能说有一个大概的了解,靠着模板才把这道题A了,只能说太弱~~!

    题意:

    初始时有一字符串,全为0.

    三种操作:

    1 k d - add  把d加到第k个数上去
    2 l r - query sum 计算l到r所有数的和
    3 l r - change to nearest Fibonacci 把l到r的数修改为距离它最近的斐波那契数

    节点附件三个值:

    s1:由lazy控制的区间的正确的和。

    s2:区间内与所有数相近的fib数之和,随着单点更新而更新。

    col:lazy,标记区间是否全部取fib数,是取1,否则取0。

    询问区间的和时,找到相应区间直接返回s1,若有col为1的区间要先向下推送,表示要取该区间的fib数的和。

    代码以及详解:

      1 #include<stdio.h>
      2 #include<math.h>
      3 #include<algorithm>
      4 using namespace std;
      5 const int maxm=100001;
      6 long long f[100]= {1,1};
      7 struct node
      8 {
      9     long long e,f;
     10     int flag;
     11 }a[maxm<<2];
     12 void pushup(int rt)
     13 {
     14     a[rt].e=a[2*rt].e+a[2*rt+1].e;
     15     a[rt].f=a[2*rt].f+a[2*rt+1].f;
     16 }
     17 void pushdown(int rt)
     18 {
     19     if(a[rt].flag)
     20     {
     21         a[2*rt].flag=1;
     22         a[2*rt+1].flag=1;
     23         a[2*rt].e=a[2*rt].f;
     24         a[2*rt+1].e=a[2*rt+1].f;
     25         a[rt].flag=0;
     26     }
     27 }
     28 
     29 //建树
     30 void build(int l,int r,int rt)
     31 {
     32     a[rt].flag=0;//先都初始化为0
     33     a[rt].e=0;//区间和初始化为0
     34     if(l==r)//如果只有一个元素
     35     {
     36         a[rt].f=1;//该节点的斐波那契数记为1,因为此时距离a[rt].e最近的斐波那契数为1
     37         return;
     38     }
     39     int m=(l+r)/2;//递归构造左右子树
     40     build(l,m,rt<<1);
     41     build(m+1,r,rt<<1|1);
     42     pushup(rt);//回溯更新父节点
     43 }
     44 
     45 
     46 void add(int pos,int m,int l,int r,int rt)
     47 {//要修改的元素的位置是pos,加数为m
     48     if(pos<l||pos>r)//如果pos不在区间范围内,则返回
     49         return ;
     50     if(l==r)//找到那个节点了
     51     {
     52         if(a[rt].flag)//如果这个节点是被处理过的,就是说它以及它的子孩子都已经是离它最近的斐波那契数了
     53         {
     54             a[rt].e=m+a[rt].f;//该节点的区间和为该节点的斐波那契数+m
     55             a[rt].flag=0;//由于节点的值已经修改过了,所以当前的a[rt].f不一定是正确的值了
     56         }
     57         else
     58             a[rt].e+=m;//没有被处理过,直接相加就好了
     59         int p=lower_bound(f,f+92,a[rt].e)-f;//二分查找:p是小于a[rt].e的第一个数
     60         if(!p)//如果不存在比a[rt].f大的数,那么离它最近的斐波那契数就是1了
     61             a[rt].f=1;
     62         else if(abs(a[rt].e-f[p])<abs(a[rt].e-f[p-1]))//比较第p个数和p-1个数哪个离a[rt].e近,把a[rt].f赋为较近的那个
     63             a[rt].f=f[p];
     64         else
     65             a[rt].f=f[p-1];
     66         return;
     67     }
     68     pushdown(rt);//延迟标记法,判断孩子节点是否需要更新,需要更新就更新
     69     int mid=(l+r)/2;
     70     if(pos<=mid)
     71         add(pos,m,l,mid,2*rt);
     72     else
     73         add(pos,m,mid+1,r,2*rt+1);
     74     pushup(rt);//回溯更新根节点
     75 }
     76 
     77 
     78 //将l到r的数赋为离他最近的斐波那契数
     79 void change(int L,int R,int l,int r,int rt)
     80 {
     81     if(R<l||L>r)
     82         return;
     83     if (L <= l && r <= R)
     84     {
     85         a[rt].e=a[rt].f;//将e的值修改为f的值
     86         a[rt].flag=1;//标记修改过了
     87         return ;
     88     }
     89     pushdown(rt);//向下更新子节点
     90     int m = (l + r) >> 1;
     91     if (L <= m) change(L , R, l,m,rt<<1);
     92     if (m < R) change(L , R ,m+1,r,rt<<1|1);
     93     pushup(rt);//回溯更新父节点
     94 }
     95 
     96 //计算l到r个数的和
     97 long long query(int L,int R,int l,int r,int rt)
     98 {
     99     if(R<l||L>r)
    100         return 0;
    101     else if (L <= l && r <= R)
    102     {
    103         return a[rt].e;
    104     }
    105     pushdown(rt);//向下更新子节点
    106     int m = (l + r) >> 1;
    107     long long ret = 0;
    108     if (L <= m) ret += query(L , R , l,m,rt*2);
    109     if (m < R) ret += query(L , R , m+1,r,rt*2+1);
    110     return ret;
    111 }
    112 int main()
    113 {
    114     int n,m,i,op;
    115     int l,r;
    116     int k,d;
    117     for(i=2; i<=92; i++)//打出斐波那契表
    118         f[i]=f[i-1]+f[i-2];
    119     while(scanf("%d%d",&n,&m)!=EOF)
    120     {
    121         build(1,n,1);//建树
    122         while(m--)//m种操作
    123         {
    124             scanf("%d",&op);
    125             if(op==1)
    126             {
    127                 scanf("%d%d",&k,&d);
    128                 add(k,d,1,n,1);//给第k个数加上d;
    129             }
    130             else
    131             {
    132                 scanf("%d%d",&l,&r);
    133                 if(op==2)
    134                     printf("%I64d
    ",query(l,r,1,n,1));//计算l到r个数的和
    135                 else
    136                     change(l,r,1,n,1);//将l到r的数赋为离他最近的斐波那契数
    137             }
    138         }
    139     }
    140     return 0;
    141 }

    现附上线段树的一些基本概念以及两个常用模板:

      

    线段树详解

    1.概述

    线段树类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,每个操作的复杂度为O(lgN)。

    线段树的性质:若父区间是[a,b].(c=(a+b)/2),左儿子区间为[a,c],右儿子区间为[c+1,b],线段树需要的空间是数组大小的四倍。

    2.基本操作

    (1)线段树的构造(void build(int node,int begin,int end);)

    运用递归的思想,如果当前结点记录的区间只有一个值,则直接赋值,                      否则递归左右子树。

    代码如下:

    #include <iostream>

    using namespace std;

    const int maxind = 256;

    int segTree[maxind * 4 + 10];  //用来存储线段树,大小要大于原数组的四倍

    int array[maxind];

    /* 构造函数,得到线段树 */

    void build(int node, int begin, int end)//node记录的是线段树的节点号

    {

        if (begin == end)

            segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */

        else

        {

            /*递归构造左右子树 */

            build(2*node, begin, (begin+end)/2);//构造左孩子

            build(2*node+1, (begin+end)/2+1, end);//构造右孩子

            /*回溯时得到当前node节点的线段信息 */

            if(segTree[2 * node] <= segTree[2 * node + 1])//把较小的数赋给左孩子

                segTree[node] = segTree[2 * node];

            else

                segTree[node] = segTree[2 * node + 1];

        }

    }

    int main()

    {

        array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3;

        build(1, 0, 5);

        for(int i = 1; i<=20; ++i)

         cout<< "seg"<< i << "=" <<segTree[i] <<endl;

        return 0;

    }

    (2)区间查询(int query(int node,int begin,int end,int left,int right);)

    主要思想是把所要查询的区间[a,b],划分为线段树上的结点,然后将这些结点代表的区间合并起来得到所需要的信息。

    代码如下:

    //其中node为当前查询结点,begin,end为当前存储的区间,left,right为此次query所要查询的区间。

    int query(int node, int begin, int end, int left, int right)    

    {   

        int p1, p2;        

        /*  查询区间和要求的区间没有交集  */  

        if (left > end || right < begin)    

            return -1;    

        /* 如果当前区间在查询区间内 ,返回该节点的值*/    

        if (begin >= left && end <= right)    

            return segTree[node];    

        /*  比较左右子树两个节点值的最小值  */  

        p1 = query(2 * node, begin, (begin + end) / 2, left, right);   

        p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);    

        if (p1 == -1)    

            return p2;    

        if (p2 == -1)    

            return p1;    

        if (p1 <= p2)    

            return  p1;    

        return  p2;      

    }   

    这样的过程一定选出了尽量少的区间,他们连后一定涵盖整个[left,right],没有重复也没有遗漏,线段树并不适合所有区间查询情况,它使用的条件是“相邻区间的信息可以被合并成两个区间合并的信息”,即问题是可以被分解解决的。

    (3)区间或结点的更新 及线段树的动态维护(线段树的核心价值所在)

    A.单节点更新

    void Updata(int node, int begin, int end, int ind, int add)/*单节点更新*/    

    {    

        if( begin == end )    

        {    

            segTree[node] += add;    

            return ;    

        }    

        int m = ( left + right ) >> 1;    

        if(ind <= m)    

            Updata(node * 2,left, m, ind, add);    

        else    

            Updata(node * 2 + 1, m + 1, right, ind, add);    

        /*回溯更新父节点*/    

        segTree[node] = min(segTree[node * 2], segTree[node * 2 + 1]);     

    B.区间更新(线段树中最有用的)

    需要用到延迟标记,每个节点新增加一个标记,用来记录这个节点是否被进行了某种修改操作(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些节点中的信息,并给这些节点标上代表这种操作的标记,在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那我们就要看p有没有标记,如果有,就按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时取+消掉p的标记。

    代码如下

    void Change(node *p, int a, int b) /* 当前考察结点为p,修改区间为(a,b]*/  

    {  

      if (a <= p->Left && p->Right <= b)  

      /* 如果当前结点的区间包含在修改区间内*/  

      {  

         ...... /* 修改当前结点的信息,并标上标记*/  

         return; 

      }  

      Push_Down(p); /* 把当前结点的标记向下传递*/  

      int mid = (p->Left + p->Right) / 2; /* 计算左右子结点的分隔点 */

      if (a < mid) Change(p->Lch, a, b); /* 和左孩子有交集,考察左子结点*/  

      if (b > mid) Change(p->Rch, a, b); /* 和右孩子有交集,考察右子结点*/  

      Update(p); /* 维护当前结点的信息(因为其子结点的信息可能有更改)*/  

    线段树的主要运用:

    1.区间最值查询问题:模板一;

    2.连续区间修改或者单节点更新的动态查询问题:模板二;

    3.多维空间的动态查询:模板三;

    典型模板:

    模板一:查询区间最值下标:

    #include <iostream>

    #include <string.h>

    using namespace std;

    #define maxind 256 //线段树节点个数

    void build(int node,int b,int e,int m[],int a[])

    {

        if(b==e)

            m[node]=b;//只有一个元素

        else

        {

            build(2*node,b,(b+e)/2,m,a);

            build(2*node+1,(b+e)/2+1,e,m,a);

            if(a[m[2*node]]<=a[m[2*node+1]])

                m[node]=m[2*node];

            else

                m[node]=m[2*node+1];

        }

    }

    int query(int node,int b,int e,int m[],int a[],int i,int j)

    {

        int p1,p2;

        if(i>e||j<b)

            return -1;

        if(b>=i&&e<=j)

            return m[node];

        p1=query(2*node,b,(b+e)/2,m,a,i,j);

        p2=query(2*node+1,(b+e)/2+1,e,m,a,i,j);

        if(p1==-1)

            return m[node]=p2;

        if(p2==-1)

            return m[node]=p1;

        if(a[p1]<=a[p2])

            return m[node]=p1;

        else

            return m[node]=p2;

    }

    int main()

    {

        int l,r;//查询区间

        scanf("%d%d",&l,&r);

        int m[maxind];//下标从1起才有意义,否则不是二叉树

        //保存下标编号结点对应区间最小值的下标

        memset(m,-1,sizeof(m));

        int a[]={3,4,5,7,2,1,0,3,4,5};

        build(1,0,sizeof(a)/sizeof(a[0])-1,m,a);

        cout<<query(1,0,sizeof(a)/sizeof(a[0])-1,m,a,l,r)<<endl;

        return 0;

    }

    模板二:

    #include<stdio.h>

    #include<algorithm>

    using namespace std;

    const int maxm=111111;

    long long add[maxm<<2];

    long long sum[maxm<<2];

    void pushup(int rt)

    {

        sum[rt]=sum[rt<<1]+sum[rt<<1|1];

    }

    void pushdown(int rt,int m)

    {

        if(add[rt])

        {

            add[rt<<1] += add[rt];

            add[rt<<1|1] += add[rt];

            sum[rt<<1] += add[rt] * (m - (m >> 1));

            sum[rt<<1|1] += add[rt] * (m >> 1);

            add[rt] = 0;

        }

    }

    void build(int l,int r,int rt)

    {

        add[rt]=0;

        if(l==r)

        {

            scanf("%lld",&sum[rt]);

            return;

        }

        int m=(l+r)/2;

        build(l,m,rt<<1);

        build(m+1,r,rt<<1|1);

        pushup(rt);

    }

    void update(int L,int R,int c,int l,int r,int rt) {

        if (L <= l && r <= R) {

            add[rt] += c;

            sum[rt] += (long long)c * (r - l + 1);

            return ;

        }

        pushdown(rt , r - l + 1);

        int m = (l + r) >> 1;

        if (L <= m) update(L , R , c , l,m,rt<<1);

        if (m < R) update(L , R , c , m+1,r,rt<<1|1);

        pushup(rt);

    }

    long long query(int L,int R,int l,int r,int rt) {

        if (L <= l && r <= R) {

            return sum[rt];

        }

        pushdown(rt , r - l + 1);

        int m = (l + r) >> 1;

        long long ret = 0;

        if (L <= m) ret += query(L , R , l,m,rt<<1);

        if (m < R) ret += query(L , R , m+1,r,rt<<1|1);

        return ret;

    }

    int main()

    {

        int n,q;

        scanf("%d%d",&n,&q);

        build(1,n,1);

        while(q--)

        {

            char op[2];

            int a,b,c;

            scanf("%s",op);

            if(op[0]=='Q'){

                scanf("%d%d",&a,&b);

                printf("%lld ",query(a,b,1,n,1));

            }

            else

            {

                scanf("%d%d%d",&a,&b,&c);

                update(a,b,c,1,n,1);

            }

        }

        return 0;

    }

      

  • 相关阅读:
    Codeforces Round #213 (Div. 2) B. The Fibonacci Segment
    关于求解不定方程的n(n-1)=2m(m-1)的解法的总结
    objective-c @()
    objective-c 条件运算符
    关于判断两个矩阵相交的一点想法
    二维几何常用运算
    《为ipad而设计 打造畅销APP》读书笔记
    ios cocos2d FPS过低的解决方法
    python 根据对象和方法名,返回提供这个方法的定义的类
    python 获取类的属性
  • 原文地址:https://www.cnblogs.com/PJQOOO/p/4651762.html
Copyright © 2011-2022 走看看