线段树的单点更新



前言

这次线段树这个部分原定3.24发,硬是拖到了今天,比较难啃,而且好像还有4个点没啃T^T

  1. 线段树 入门
    http://blog.csdn.net/zearot/article/details/52280189
  2. 线段树 详解
    https://www.cnblogs.com/AC-King/p/7789013.html
  3. 线段树 约瑟夫环
    http://blog.csdn.net/acceptedxukai/article/details/6926431
  4. 线段树
    https://wenku.baidu.com/view/71fc1659ba1aa8114431d97b.html

不会做的题

  1. Buy Tickets - POJ - 2828
  2. Points - CodeForces - 19D


敌兵布阵 - HDU 1166

题目大意
此处省略1e9个字

思路
单点更新的模板题来着的

  #include <cstdio>
  #include <cmath>
  #include <climits>
  #include <cstring>
  #include <iostream>
  #define lson l, m, rt << 1          //经常用所以用define
  #define rson m + 1, r, rt << 1 | 1
  using namespace std;
  const int N = 50005;

  int sum[N << 2], add[N << 2];
  int a[N];

  void pushUp(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; }    //本题线段树维护的是区间和
  void build(int l, int r, int rt = 1){
      if(l == r){
          sum[rt] = a[l];
          return;
      }
      int m = (l + r) >> 1;
      build(lson);    //递归建树
      build(rson);
      pushUp(rt);     //更新信息
  }
  void update(int p, int val, int l, int r, int rt = 1){
      if(l == r){
          sum[rt] += val;                   //更新
          return;
      }
      int m = (l + r) >> 1;
      if(p <= m)    update(p, val, lson);   //找到p点在树中的位置
      else          update(p, val, rson);
      pushUp(rt);
  }
  int query(int ql, int qr,int l, int r, int rt = 1){
      if(ql <= l && r <= qr){               //[l,r]在[ql,qr]中则返回[l,r]中的区间和,剩下部分怎么办?
          return sum[rt];                   //(接上句)交由其他递归去完成
      }
      int m = (l + r) >> 1;
      int ans = 0;
      if(ql <= m)     ans += query(ql, qr, lson);
      if(qr > m)      ans += query(ql, qr, rson);
      return ans;
  }

  int main(){
      int t;
      char ops[10];
      scanf("%d", &t);
      for(int caseno = 1; caseno <= t; caseno++){
          int n;
          scanf("%d", &n);
          for(int i = 1; i <= n; i++){
              scanf("%d", &a[i]);
          }
          build(1, n);

          printf("Case %d:\n", caseno);
          int a, b;
          while(scanf("%s", ops)){
              if(strcmp(ops, "End") == 0)  break;
              scanf("%d%d", &a, &b);
              if(strcmp(ops, "Query") == 0){
                  printf("%d\n", query(a, b, 1, n));
              }
              if(strcmp(ops, "Add") == 0){
                  update(a, b, 1, n);
              }
              if(strcmp(ops, "Sub") == 0){
                  update(a, -b, 1, n);
              }
          }
      }
      return 0;
  }


Billboard - HDU 2795

题目大意
要在h*w的公告牌上贴1*wi的公告,尽量贴在上面,在此基础上尽量贴在左边,给定每一个wi,求它贴在第几行

思路
一开始看似乎和线段树没什么关系……
不过想想,我们可以用线段树维护每一行剩下多少,然后维护区间最大值,如果最大值都小过现在要贴的公告,那就不要递归那个子树就行,然后优先递归左子树

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #define lson l, m, rt << 1
  #define rson m + 1, r, rt << 1 | 1
  typedef long long ll;
  const int N = 200005;
  const ll INF = 0x3f3f3f3f;
  using namespace std;

  int sum[N << 2];

  void pushUp(int rt) { sum[rt] = max(sum[rt << 1], sum[rt << 1 | 1]); }
  void build(int val, int l, int r, int rt = 1){
      if(l == r){
          sum[rt] = val;
          return;
      }
      int m = (l + r) >> 1;
      build(val, lson);
      build(val, rson);
      pushUp(rt);
  }

  void update(int p, int val, int l, int r, int rt = 1){
      if(l == r){
          sum[rt] -= val;
          return;
      }
      int m = (l + r) >> 1;
      if(p <= m)  update(p, val, lson);
      else        update(p, val, rson);
      pushUp(rt);
  }

  int query(int val, int l, int r, int rt = 1){
      if(l == r){
          return  sum[rt] >= val ? l : -1;      //小于val就返回-1
      }
      int m = (l + r) >> 1;
      int ans = -1;     //当下面两个都跑不出结果时可返回-1
      if(sum[rt << 1] >= val)                  ans = query(val, lson);    //优先跑左子树,在val不超过左子树区间最小值的情况下
      if(ans <= 0 && sum[rt << 1 | 1] >= val)  ans = query(val, rson);    //有答案了就不用再跑了,没答案也要满足val不超过有子树最小值
      return ans;   
  }

  int main(){
      int h, w, q;
      while(~scanf("%d%d%d", &h, &w, &q)){
          h = min(h, q);    //一点点小坑,题目给的h的范围很大,但是只要q和h取最小就可以了,q的上界足够小能开数组  
          build(w, 1, h);
          while(q--){
              int val, ans;
              scanf("%d", &val);
              ans = query(val, 1, h);
              if(ans > 0){
                  update(ans, val, 1, h);
              }
              printf("%d\n", ans);
          }
      }
      return 0;
  }


Queue - CodeForces 91B

题目大意
给定一个数组,如果存在i < j但是ai > aj,则i对应的值为 j - i - 1 的最大值(也就是说j取最大能满足ai > aj的),现求出每个i的对应值

思路
用线段树维护一下每个区间段内的最小值,那么查询时便无需查询ai < sum[rt]的部分,可以快速得到解
另外本题求最大的j,所以优先递归右子树,和上题类似

  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <vector>
  #define lson l, m, rt << 1
  #define rson m + 1, r, rt << 1 | 1
  #define mid int m = (l + r) >> 1
  typedef long long ll;
  const int N = 1e5 + 15;
  const ll INF = 0x3f3f3f3f;
  using namespace std;

  int a[N], sum[N << 2], ans[N];

  void pushUp(int rt){ sum[rt] = min(sum[rt << 1], sum[rt << 1 | 1]); }
  void build(int l, int r, int rt = 1){
      if(l == r){
          sum[rt] = a[l];
          return;
      }
      mid;
      build(lson);
      build(rson);
      pushUp(rt);
  }
  int query(int p, int val, int l, int r, int rt = 1){
      if(l == r){
          return l;
      }
      mid;
      if(p > m || sum[rt << 1 | 1] < val)   return query(p, val, rson);
      else                                  return query(p, val, lson);
  }

  int main(){
      int n;
      while(~scanf("%d", &n)){
          for(int i = 1; i <= n; i++){
              scanf("%d", &a[i]);
          }
          build(1, n);
          for(int i = 1; i <= n; i++){
              printf((i < n ? "%d " : "%d\n"), query(i, a[i], 1, n) - i - 1);
          }
      }
      return 0;
  }


Who Gets the Most Candies - POJ-2886

题目大意
有N个小孩围成一圈,他们的编号顺时针编号从1到N,每个小孩手上有一张写着整数的卡片,现在从第k个小孩开始出圈。第k个小孩手上的卡片假设为A,如果A是正数,则下一个出圈的小孩为从第k个小孩开始的顺时针方向的第A个,如果是负数,则是逆时针方向的第A个。现在第i个出圈的小孩可以获得f(i)个糖果,f(i)为i的因子数,现在问谁获得的苹果最多?(如果有多个出圈的小孩获得同样数量的苹果,取最前面的小孩)

思路
啃了线段树单点更新 + 线段树模拟约瑟夫环 + 反素数后,这题就没那么难
本质上本题求 1 - N 的反素数,根据题的范围可以离线打表

  #include <iostream>
  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  typedef long long ll;

  /*  离线打表法
  const int N = 500000 + 5;
  int a[N];
  int anti_prime[N], anti_prime_cnt[N], pos;
  int prime[18] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 57};

  void dfs(int depth, int cnt, int num){
      if(a[num] < cnt){
          a[num] = cnt;
      }
      for(int i = 1; i <= 30; i++){
          if(num > N/prime[depth])    break;
          num *= prime[depth];
          dfs(depth + 1, cnt*(i + 1), num);
      }
  }

  int main(){
      memset(a, 0, sizeof(a));
      dfs(1, 1, 1);
      pos = 0;
      int x = 0;
      for(int i = 1; i < N; i++){
          if(a[i] > x){
              anti_prime[pos] = i;
              anti_prime_cnt[pos++] = a[i];
              x = a[i];
          }
      }
      printf("int anti_prime[%d] = {", pos);
      printf("%d", anti_prime[0]);
      for(int i = 1; i < pos; i++){
          printf(", %d", anti_prime[i]);
      }
      printf("};\n");
      printf("int anti_prime_cnt[%d] = {", pos);
      printf("%d", anti_prime_cnt[0]);
      for(int i = 1; i < pos; i++){
          printf(", %d", anti_prime_cnt[i]);
      }
      printf("};\n");
      return 0;
  }*/

  #define lson l, m, rt << 1
  #define rson m + 1, r, rt << 1 | 1
  #define mid int m = (l + r) >> 1
  const int N = 500000 + 5;
  int  sum[N << 2], a[N << 2], b[N << 2];
  int  anti_prime[35]     = {1, 2, 4, 6, 12, 24, 36, 48, 60, 120, 180, 240, 360, 720, 840, 1260, 1680, 2520, 5040, 7560, 10080, 15120, 20160, 25200, 27720, 45360, 50400, 55440, 83160, 110880, 166320, 221760, 277200, 332640, 498960};
  int  anti_prime_cnt[35] = {1, 2, 3, 4, 6, 8, 9, 10, 12, 16, 18, 20, 24, 30, 32, 36, 40, 48, 60, 64, 72, 80, 84, 90, 96, 100, 108, 120, 128, 144, 160, 168, 180, 192, 200};
  char str[N][15], pos;
  int  val[N];

  void pushUp(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; }
  void build(int l, int r, int rt = 1){
      a[rt] = l;
      b[rt] = r;
      if(l == r){
          sum[rt] = 1;
      }else{
          mid;
          build(lson);
          build(rson);
          pushUp(rt);
      }
  }
  int update(int p, int rt = 1){
      int ans;
      if(a[rt] == b[rt]){
          sum[rt] = 0;
          return a[rt];
      }else{
          if(p <= sum[rt << 1])       ans = update(p, rt << 1);
          else                        ans = update(p - sum[rt << 1], rt << 1 | 1);  //进入右子树要变换相对位置
      }
      pushUp(rt);
      return ans;
  }
  void init(){
      pos = 0;
  }
  void solve(int n, int start){
      init();
      build(1, n);
      int m = start;
      int seq = 1;  //表示当前序列的第几个人,不代表编号
      int pend = upper_bound(anti_prime, anti_prime + 35, n) - 1 - anti_prime;
      int end  = anti_prime[pend];
      int t = 0;    //循环次数控制
      int ans;
      while(t < end){
          seq--;
          if(m >= 0){   //非负则是 m - 1 个,因为第seq个人GG后,它的下一个人变成第seq个人,本身就是1了
              seq = ((seq + m - 1)%sum[1] + sum[1])%sum[1];
          }else{        //负则是 m 个
              seq = ((seq + m)%sum[1] + sum[1])%sum[1];
          }
          seq++;
          int k = update(seq);    //更新顺便找编号
          m = val[k];
          t++;
          if(t == end)    ans = k;
      }
      printf("%s %d\n", str[ans], anti_prime_cnt[pend]);
  }

  int main(){
      int n, start;
      while(~scanf("%d%d", &n, &start)){
          for(int i = 1; i <= n; i++){
              scanf("%s%d", str[i], &val[i]);
          }
          solve(n, start);
      }
  }