zoukankan      html  css  js  c++  java
  • 知识点

    知识点 - 线段树 权值 树套树 二维 可持续

    //区间更新求和
    inline int ls(int p) { return p << 1; }//左儿子 
    inline int rs(int p) { return p << 1 | 1; }//右儿子 
    void push_up(int p) {
    	t[p] = t[ls(p)] + t[rs(p)];
    }// 向上不断维护区间操作 
    void build(ll p, ll l, ll r) {
    	if (l == r) { t[p] = a[l]; return; }
    	ll mid = (l + r) >> 1;
    	build(ls(p), l, mid);
    	build(rs(p), mid + 1, r);
    	push_up(p);
    }
    inline void f(ll p, ll l, ll r, ll k) {
    	tag[p] = tag[p] + k;
    	t[p] = t[p] + k * (r - l + 1);
    }
    inline void push_down(ll p, ll l, ll r) {
    	ll mid = (l + r) >> 1;
    	f(ls(p), l, mid, tag[p]);
    	f(rs(p), mid + 1, r, tag[p]);
    	tag[p] = 0;
    }
    inline void update(ll ul, ll ur, ll l, ll r, ll p, ll k) {
    	//ul,ur为要修改的区间
    	//l,r,p为当前节点所存储的区间以及节点的编号 
    	if (ul <= l && r <= ur) {
    		t[p] += k * (r - l + 1);
    		tag[p] += k;
    		return;
    	}
    	push_down(p, l, r);
    	ll mid = (l + r) >> 1;
    	if (ul <= mid)update(ul, ur, l, mid, ls(p), k);
    	if (ur>mid) update(ul, ur, mid + 1, r, rs(p), k);
    	push_up(p);
    }
    ll query(ll ql, ll qr, ll l, ll r, ll p) {
    	ll res = 0;
    	if (ql <= l && r <= qr)return t[p];
    	ll mid = (l + r) >> 1;
    	push_down(p, l, r);
    	if (ql <= mid)res += query(ql, qr, l, mid, ls(p));
    	if (qr>mid) res += query(ql, qr, mid + 1, r, rs(p));
    	return res;
    }
    

    解决问题类型:

    区间rmq即其他符合结合律的运算,支持区间更新。

    想法题

    1. 【二分】找第k个0:结点维护0的个数cnt[rt],然后在树上队cnt二分。

    2. 【二分】找第k大(权值线段树):这个问题可以排序或nth_element(start,start+first,start+last).

      考虑线段树的做法,我们先将n个数离散化到[1,n]范围.建一个1~n的空树,每个结点维护tot即该区间有几个数。然后将n个数字依次插入:

      void insert(int x,int root = 1)
      {
          if(seg[root].l == seg[root].r)
          {
              set[root].tot++;
              return ;
          }
          int mid = seg[root].l + seg[root].r >> 1;
          if(x <= mid)//在左儿子当中
              insert(k,lson);
          if(x > mid)//在右儿子当中
              insert(k,rson);
          //Push up the information
      }
      

      查找:

      int query(int k,int root = 1)
      {
          if(seg[root].l == seg[root].r)
              return seg[root].l;//找到了
          int mid = seg[lson].tot;//左儿子区间的数字个数
          if(k <= mid)
              return query(k,lson);//左儿子里面找
          else if(k > mid)
              return query(k - mid,rson);//右儿子里面找
      }
      

      这就是权值线段树,所有叶子节点相当于一个统计每个数出现次数的cnt数组,然后再把这些信息求和地push_up

    3. 【二分】给定x,找最小的 i 使得(prefixSum[i]>x):树上二分即可。

    4. 【dp】找最大子段和。你可以dp O(n)做,也可以线段树维护四个值:区间和,区间最大子段和,最大前缀,最大后缀。

      push_up 这么写(dp的感觉)。

      struct data {
          int sum, pref, suff, ans;
      };
      
      data combine(data l, data r) {
          data res;
          res.sum = l.sum + r.sum;
          res.pref = max(l.pref, l.sum + r.pref);
          res.suff = max(r.suff, r.sum + l.suff);
          res.ans = max(max(l.ans, r.ans), l.suff + r.pref);
          return res;
      }
      
    5. 【树套数据结构】无序区间lower_bound():每个结点存该区间的有序序列。(O(n log n))空间

      vector<int> t[4*MAXN];
       //O(n log n)建树
      void build(int a[], int v, int tl, int tr) {
          if (tl == tr) {
              t[v] = vector<int>(1, a[tl]);
          } else { 
              int tm = (tl + tr) / 2;
              build(a, v*2, tl, tm);
              build(a, v*2+1, tm+1, tr);
              merge(t[v*2].begin(), t[v*2].end(), t[v*2+1].begin(), t[v*2+1].end(),
                    back_inserter(t[v]));
          }
      }
      //O(log^2n)询问
      int query(int v, int tl, int tr, int l, int r, int x) {
          if (l > r)
              return INF;
          if (l == tl && r == tr) {
              vector<int>::iterator pos = lower_bound(t[v].begin(), t[v].end(), x);
              if (pos != t[v].end())
                  return *pos;
              return INF;
          }
          int tm = (tl + tr) / 2;
          return min(query(v*2, tl, tm, l, min(r, tm), x), 
                     query(v*2+1, tm+1, tr, max(l, tm+1), r, x));
      }
      //若想单点更新,每个结点存multiset 建树时空花费为O(nlog^2n)
      void update(int v, int tl, int tr, int pos, int new_val) {
          t[v].erase(t[v].find(a[pos]));
          t[v].insert(new_val);
          if (tl != tr) {
              int tm = (tl + tr) / 2;
              if (pos <= tm)
                  update(v*2, tl, tm, pos, new_val);
              else
                  update(v*2+1, tm+1, tr, pos, new_val);
          } else {
              a[pos] = new_val;
          }
      }
      
    6. 【fractional cascading】 把上一个问题优化为(O(log n))

      考虑子问题:有n个数,被分成k个有序序列,分别对每个序列询问lower_bound,如何将(O(klogfrac{n}{k})) 优化为(O(logn))

      一个想法是对每个数预处理它在其它k-1个序列中lower_bound的结果(假装没有时间花费吧,暂时想不出有什么好办法。)。这样直接对合并的序列直接二分,然后查预处理的表即可。不过空间花费为O(kn)

      如何优化为O(n)??? 看不懂。。。

    7. 【二维】单点修改,询问某个子矩阵最大值。先对x坐标建树tree_x,对它的每个结点都建一个线段树tree_y

      //空间O(16nm)
      void build_y(int vx, int lx, int rx, int vy, int ly, int ry) {
          if (ly == ry) {
              if (lx == rx)
                  t[vx][vy] = a[lx][ly];
              else
                  t[vx][vy] = t[vx*2][vy] + t[vx*2+1][vy];
          } else {
              int my = (ly + ry) / 2;
              build_y(vx, lx, rx, vy*2, ly, my);
              build_y(vx, lx, rx, vy*2+1, my+1, ry);
              t[vx][vy] = t[vx][vy*2] + t[vx][vy*2+1];
          }
      }
      
      void build_x(int vx, int lx, int rx) {
          if (lx != rx) {
              int mx = (lx + rx) / 2;
              build_x(vx*2, lx, mx);
              build_x(vx*2+1, mx+1, rx);
          }
          build_y(vx, lx, rx, 1, 0, m-1);
      }
      int sum_y(int vx, int vy, int tly, int try_, int ly, int ry) {
          if (ly > ry) 
              return 0;
          if (ly == tly && try_ == ry)
              return t[vx][vy];
          int tmy = (tly + try_) / 2;
          return sum_y(vx, vy*2, tly, tmy, ly, min(ry, tmy))
               + sum_y(vx, vy*2+1, tmy+1, try_, max(ly, tmy+1), ry);
      }
      
      int sum_x(int vx, int tlx, int trx, int lx, int rx, int ly, int ry) {
          if (lx > rx)
              return 0;
          if (lx == tlx && trx == rx)
              return sum_y(vx, 1, 0, m-1, ly, ry);
          int tmx = (tlx + trx) / 2;
          return sum_x(vx*2, tlx, tmx, lx, min(rx, tmx), ly, ry)
               + sum_x(vx*2+1, tmx+1, trx, max(lx, tmx+1), rx, ly, ry);
      }
      void update_y(int vx, int lx, int rx, int vy, int ly, int ry, int x, int y, int new_val) {
          if (ly == ry) {
              if (lx == rx)
                  t[vx][vy] = new_val;
              else
                  t[vx][vy] = t[vx*2][vy] + t[vx*2+1][vy];
          } else {
              int my = (ly + ry) / 2;
              if (y <= my)
                  update_y(vx, lx, rx, vy*2, ly, my, x, y, new_val);
              else
                  update_y(vx, lx, rx, vy*2+1, my+1, ry, x, y, new_val);
              t[vx][vy] = t[vx][vy*2] + t[vx][vy*2+1];
          }
      }
      
      void update_x(int vx, int lx, int rx, int x, int y, int new_val) {
          if (lx != rx) {
              int mx = (lx + rx) / 2;
              if (x <= mx)
                  update_x(vx*2, lx, mx, x, y, new_val);
              else
                  update_x(vx*2+1, mx+1, rx, x, y, new_val);
          }
          update_y(vx, lx, rx, 1, 0, m-1, x, y, new_val);
      }
      
    8. 【二维】如果给出n个坐标,询问矩阵里有几个坐标,我们可以将空间优化到(O(n log n))

      具体来说,对于某个tree_x上的结点,如果它的区间是([l,r]),那么我们只用这个区间的点建树

    9. 【主席树】每次更新前都存一次历史版本(git-hub??)。考虑到线段树每次更新的部分很少(O(logn)长的链)所以每次只是创建一个新的链,然后把它连到上一个版本上。这样就要用到指针。

      //单点更新,区间求和的可持续化线段树
      struct Vertex {
          Vertex *l, *r;
          int sum;
      
          Vertex(int val) : l(nullptr), r(nullptr), sum(val) {}
          Vertex(Vertex *l, Vertex *r) : l(l), r(r), sum(0) {
              if (l) sum += l->sum;
              if (r) sum += r->sum;
          }
      };
      
      Vertex* build(int a[], int tl, int tr) {
          if (tl == tr)
              return new Vertex(a[tl]);
          int tm = (tl + tr) / 2;
          return new Vertex(build(a, tl, tm), build(a, tm+1, tr));
      }
      
      int get_sum(Vertex* v, int tl, int tr, int l, int r) {
          if (l > r)
              return 0;
          if (l == tl && tr == r)
              return v->sum;
          int tm = (tl + tr) / 2;
          return get_sum(v->l, tl, tm, l, min(r, tm))
               + get_sum(v->r, tm+1, tr, max(l, tm+1), r);
      }
      
      Vertex* update(Vertex* v, int tl, int tr, int pos, int new_val) {
          if (tl == tr)
              return new Vertex(new_val);
          int tm = (tl + tr) / 2;
          if (pos <= tm)
              return new Vertex(update(v->l, tl, tm, pos, new_val), v->r);
          else
              return new Vertex(v->l, update(v->r, tm+1, tr, pos, new_val));
      }
      
    10. 【主席树】区间第k大:

      ​ 0.将问题化简为(0 le a[i] lt n) ,知求前缀的第k大。

      ​ 1.前缀第k大可以用权值线段树解决。于是我们可持久化地建n颗树。

      2.对于a[i]取值任意的,用离散化变成(0 le a[i] lt n)

      ​ 3.对于任意区间,我们在树上二分时将tot[x]变为第 l 颗减第 r-1 颗的即可。

    复杂度:

    (o(nlogn))

    例题

    A - Who has a better strategy ?:

    暂时超出理解范围

    用怪的强度作为下标,维护两个人打怪的时间,在线段树上二分找答案即可。

    C - Super Mario

    区间小于x的有几个数。

    B - 永无乡

    线段树合并模板题

    D - To the moon

    区间更新,询问历史版本区间和,回到历史版本。

    标记永久化,计算下每个标记的贡献即可。

    E - Kth number

    F - Count on a tree

    询问树上两点之间的链第k大。

    维护树上前缀,和树上距离公式一样处理

    G - D-query

    询问区间几个不同的树

    可能有些人还不理解,放一个离线版本的。

    H - Query on A Tree

    询问x异或子树u里的某个结点的最大值

    可持久化字典树,用dfs序就可以变成区间询问。

    I - Army Formations

    给出一棵树共nn个顶点,每个顶点有一个权值(val_i),你需要对每个节点统计一个最优解
    每个节点的解按照一定规则产生:取出该节点的子树下所有的顶点,把顶点任意排序成一个序列,设为(v1,v2...,vk)
    此时解为(sum_{i=1}^{k}sum_{j=1}^{i}val_{v_j}),最小的解为最优解

    线段树合并, 维护前缀和的前缀和。维护每个区间前缀和的前缀和,前缀和,个数,就可以向上合并,叶子结点的计算就是等差数列求和。

    J - Lovers

    初始n个空串,m个操作:

    1.给[l,r]的所有字符串头尾加一个‘d’,将原字符串x变为dxd

    2.求[l,r]所有字符串代表的数字之和mod 1e9+7

    更新操作时可以合并的。合并两个操作,只用维护那个数字正着和倒着的大小以及长度就可以。快速更新一段区间,也只用知道区间所有数字的10的长度次的和就可以了。

    K - The Child and Sequence

    给一个序列
    支持3种操作
    1 u v 对于所有i u<=i<=v,输出a[i]的和
    2 u v t 对于所有i u<=i<=v a[i]=a[i]%t
    3 u v 表示a[u]=v(将v赋值给a[u])
    n,q<=1e5 a[i],t,v<=1e9

    维护区间最值快速判断还有没有数字需要更新,有的话,暴力更新就好了。

    代码

    成功的路并不拥挤,因为大部分人都在颓(笑)
  • 相关阅读:
    Script file 'D:Anaconda3Scriptspip-script.py' is not present.
    Python中*args和**kwargs
    Error: [Errno 11001] getaddrinfo failed
    form表单怎么用a标签提交post数据
    解决Pycharm运行卡顿问题
    Ubuntu下安装QQ微信——亲测试有效!
    js文件中发送ajax请求,ulr路径不起作用的解决办法
    django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3
    python中print打印显示颜色
    文件保护
  • 原文地址:https://www.cnblogs.com/SuuT/p/11366382.html
Copyright © 2011-2022 走看看