• 【题解】LibreOJ #6281. 数列分块入门 5


    题目链接:LibreOJ #6281. 数列分块入门 5

    题面

    题目描述

    给定一个长为 (n) 的数列 (a_1dots a_n),以及 (n) 个操作,操作涉及区间开方,区间求和。

    输入格式

    第一行输入一个数字 (n)

    第二行输入 (n) 个数字,第 (i) 个数字为 (a_i),以空格隔开。

    接下来输入 (n) 行询问,每行输入四个数字 ( m{opt},l,r,c),以空格隔开。

    ( m opt=0),表示将位于 (left[l, r ight]) 的之间的数字都开方。对于区间中每个 (a_ileft(lleq ileq r ight)longleftarrow lfloor sqrt a_i floor)

    ( m opt=1),表示询问位于 (left[l, r ight]) 的所有数字的和。

    输出格式

    对于每次询问,输出一行一个数字表示答案。

    样例(很水)

    Input

    4
    1 2 2 3
    0 1 3 1
    1 1 4 4
    0 1 2 2
    1 1 2 4
    

    Output

    6
    2
    

    数据范围与提示

    对于 (100\%) 的数据,(1leq nleq 50000,-2^{31}leq { m others})({ m ans}leq 2^{31}-1)

    写博客时的吐槽

    ( m LaTeX)是真nm难打啊。。

    思路

    审题

    与平时最简单的数列分块入门的区别在于,这个数列分块入门五并不是加上or减去一个数,而是开方。这两者的区别在于,加上或者减去一个数与这个数本身无关,可以直接相加减,可以直接做记号表示这个块加上或减去了某个数。

    加上或减去某个数时,在完整块中,这一个块每一个数增加或减少的值都相同,就可以很容易地给这一整个块做标记。

    但是做开方运算时(其实乘方、乘除都很坑),每一个块增加或者减小的值不相等,就导致了不能把这一整个块增加或减小的值做一个标记,这就相当于把分块废掉了。

    解法

    这是一道题目,总不可能没有解法吧。

    我们用到了下面这个( m sol())函数来解决这个问题。

    void sol(int x) {
      if (flag[x]) return; //如果这个块被标记了,就废了,直接返回,节省时间
                           //标记原则后面会讲
      flag[x] = true;      //先把这个块标记
      sum[x] = 0;          //将sum[]数组初始化为零
      for (int i = (x - 1) * len + 1; i <= x * len; i++) { //循环块内每一个值
        a[i] = sqrt(a[i]), sum[x] += a[i]; //对块内每一个数都做开方运算,再把他们的和存进sum[]数组里面
        if (a[i] > 1) flag[x] = false;     //如果这一个值还大于1,也就是还能够进行开方,这个块就还能继续开放,把flag[]标记去除
      }
      return;
    }
    

    从上面的函数中可以看出,我们把已经不能再开方的块进行跳过,大大节约了程序开方的时间。这就是代码的关键。

    剩下的地方就和普通的数列分块差不多了。

    代码

    #include <cstdio> //getchar()和putchar()要用
    #include <cmath>  //取块大小和开方时需要用到sqrt()函数
    
    int b[50005], a[50005], sum[255];
    bool flag[255];
    int len;
    
    int read() {//快速读入,因为LibreOJ默认吸氧,所以没有了前面的inline
      int X = 0; bool flag = 1; char ch = getchar();
      while (ch < '0' || ch > '9') {if (ch == '-') flag = 0; ch = getchar();}
      while (ch >= '0' && ch <= '9') {X = (X << 1) + (X << 3) + ch - '0'; ch = getchar();}
      if (flag) return X;
      return ~ (X - 1);
    }
    
    void write(int X) {//快速输出,同上
      if (X < 0) {putchar('-'); X = ~ (X - 1);}
      int s[50], top = 0;
      while (X) {s[++top] = X % 10; X /= 10;}
      if (!top) s[++top] = 0;
      while (top) putchar(s[top--] + '0');
      putchar('
    ');
      return;
    }
    
    void sol(int x) {
      if (flag[x]) return; //如果这个块被标记了,就废了,直接返回,节省时间
      flag[x] = true;      //先把这个块标记
      sum[x] = 0;          //将sum[]数组初始化为零
      for (int i = (x - 1) * len + 1; i <= x * len; i++) { //循环块内每一个值
        a[i] = sqrt(a[i]), sum[x] += a[i]; //对块内每一个数都做开方运算,再把他们的和存进sum[]数组里面
        if (a[i] > 1) flag[x] = false;     //如果这一个值还大于1,也就是还能够进行开方,这个块就还能继续开放,把flag[]标记去除
      }
      return;
    }
    
    void add(int l, int r) {
      for (int i = l; i <= (b[l] * len < r ? b[l] * len : r); i++) { //从左往右暴力循环左侧不完整块
        sum[b[l]] -= a[i];
        a[i] = sqrt(a[i]);
        sum[b[l]] += a[i];
      }
      if (b[l] != b[r]) //如果l和r不在同一块内
        for (int i = r; b[i] == b[r]; i--) { //同右往左暴力循环右侧不完整块
    	//循环条件这里我原来一直写i == b[r]就错了,要注意,下面query()函数也是
          sum[b[r]] -= a[i];
          a[i] = sqrt(a[i]);
          sum[b[r]] += a[i];
        }
      for (int i = b[l] + 1; i <= b[r] - 1; i++) sol(i); //中间完整块使用sol()函数解决
      return;
    }
    
    int query(int l, int r) { //查询函数,与普通分块差不多
      int ans = 0;
      for (int i = l; i <= (b[l] * len < r ? b[l] * len : r); i++) ans += a[i];//左侧
      if (b[l] != b[r]) for (int i = r; b[i] == b[r]; i--) ans += a[i];//右侧
      for (int i = b[l] + 1; i <= b[r] - 1; i++) ans += sum[i];//中间
      return ans;
    }
    
    int main() {
      int n;
      n = read();
      len = sqrt(n);
      for (int i = 1; i <= n; i++) a[i] = read();
      for (int i = 1; i <= n; i++) {
        b[i] = (i - 1) / len + 1;
        sum[b[i]] += a[i];
      }
      for (int i = 1; i <= n; i++) {
        bool opt; int l, r;
        opt = read(); l = read(); r = read(); read();
        opt ? write(query(l, r)) : add(l, r);
        //三目运算符,如果opt==1就add(),else就query()
      }
      return 0;
    }
    
  • 相关阅读:
    给VPS装桌面
    GIT免密码PUSH
    验证码类
    Url几个常用的函数
    PHP--关于模板的原理和解析
    管理员权限执行批处理和静默执行regsvr32注册
    linux 维护常见场景小命令 (未完待续)
    批处理定时重启print打印服务,解决打印机异常队列堆积
    Linux----LVM扩容磁盘空间
    6、Samba 服务器配置
  • 原文地址:https://www.cnblogs.com/g-mph/p/14617976.html
Copyright © 2020-2023  润新知