首页 > 其他分享 >分块,优雅的暴力

分块,优雅的暴力

时间:2023-07-31 17:55:47浏览次数:34  
标签:暴力 分块 int 复杂度 sqrt 优雅 add 区间

来看一下一个例题:

  • 现在给出一个长度为 \(N\) 序列 A,定义两个操作如下:

  • 1 l r v,表示从 \(A_l \sim A_r\) 每个数都加上 \(v\)。

  • 2 l r,对 \(A_l \sim A_r\) 求和。

传统的线段树可以很优秀地实现这两个操作,但是需要打 \(lazytag\)。

同时因为线段树(非动态开点)的空间复杂度为 \(O(4N)\),在空间限制或数据范围较大的题目中容易被卡,于是考虑用时间换空间,开发空间复杂度更优的算法。

分块就是这样的算法。

分块算法将整个序列分为若干个长度不超过 \(\sqrt n\) 的区间,并对于每个区间维护两个标记增量标记 \(add\) 和区间和 \(sum\)。其中增量标记 \(add\) 用来记录对于整个块的加法对答案产生的贡献,区间和 \(sum\) 用来记录整个区间内增量标记 \(add\) 之外的答案。

所以区间和查询就较为简单了。对于 \([l,r]\) 区间内的每个块,直接算 \(sum+add*(R_i-L_i+1)\) 计入答案即可,其中 \(L_i\) 和 \(R_i\) 分别表示第 \(i\) 个区间的左右端点。而对于区间两端不被整个分块覆盖的位置直接暴力统计即可。记 \(l,r\) 所在区间编号分别为 \(p,q\),则两端的查询答案即为

\[\sum_{i=l}^{R_p}a_i+add_p\times(R_p-l+1)+\sum_{i=L_q}^{r}a_i+add_q\times(r-L_q+1) \]

于是将中间部分的答案和两端的答案直接合并就可以得到查询操作的答案了。

PS:如果 \(l,r\) 属于同一区间,则直接暴力统计即可。

inline int query(int l, int r) {
    int res = 0, p = pos[l], q = pos[r];
    if(p == q) {
        for(int i = l; i <= r; i++) {
            res += a[i] + add[i];
        }
        return res;
    }
    else {
        for(int i = p + 1; i < q; i++) {
            res += sum[i] + add[i] * (R[i] - L[i] + 1);
        }
        for(int i = l; i <= R[p]; i++) {
            res += a[i];
        }
        res += add[p] * (R[p] - l + 1);
        for(int i = L[q]; i <= r; i++) {
            res += a[i];
        }
        res += add[q] * (r - L[q] + 1);
        return res;
    }
}

如此暴力的做法复杂度是如何保证的呢?

观察到由于分块时每个区间长度均不超过 \(\sqrt N\),于是中间统计部分最多不超过 \(\lceil \sqrt N \rceil\) 个区间,而两端暴力统计的次数总的不超过 \(2\sqrt N\),所以整个查询的复杂度可以近似视作 \(O(\sqrt N)\)。

对于修改操作可以用相同的思想,每个完整区间直接加在增量标记 \(add\) 上,不完整区间则暴力往原序列 \(A_i\) 和区间和 \(sum\) 上加。总复杂度用相同的方法分析可以得到也为 \(O(\sqrt N)\)。

inline void change(int l, int r, int v) {
    int p = pos[l], q = pos[r];
    if(p == q) {
        for(int i = l; i <= r; i++) {
            a[i] += v;
        }
        sum[p] += (r - l + 1) * v;
    }
    else {
        for(int i = p + 1; i < q; i++) {
            add[i] += v;
        }
        for(int i = l; i <= R[p]; i++) {
            a[i] += v;
        }
        sum[p] += (R[p] - l + 1) * v;
        for(int i = L[q]; i <= r; i++) {
            a[i] += v;
        }
        sum[q] += (r - L[q] + 1) * v;
    }
}

算法的预处理部分要处理的为每个区间的左右端点 \(L_i,R_i\),每个位置 \(i\) 所对应的区间 \(pos_i\),和原序列的区间和 \(sum_j\),总复杂度为 \(O(N\sqrt N)\)

对于最后一个长度不满 \(\sqrt N\) 的区间直接处理即可。

    t = sqrt(n);
    for(int i = 1; i <= t; i++) {
        L[i] = (i - 1) * sqrt(n) + 1;
        R[i] = i * sqrt(n);
    }
    if(R[t] < n) {
        t++;
        L[t] = R[t - 1] + 1;
        R[t] = n;
    }
    for(int i = 1; i <= t; i++) {
        for(int j = L[i]; j <= R[i]; j++) {
            pos[j] = i;
            sum[i] += a[j];
        }
    }

若序列长度为 \(N\),总操作数为 \(Q\),则由上述分析可得分块算法的时间复杂度上界为 \(O((N+Q)\sqrt N)\),空间复杂度为 \(O(N)\)。

在处理区间问题时,通常有树状数组、线段树和分块三种方法,三种方法各有优劣。

  • 树状数组效率最高,代码最短;但不易扩展,不够直观,调试难度大。

  • 线段树效率较高,扩展性好;但代码较长,且直观性一般,调试难度较大。

  • 分块算法最为通用且直观,且代码较短;但效率一般。在复杂度允许,且正确性有保证的情况下,写分块的考场收益一般最大。

分块还可以较快地求区间不超过 \(k\) 的数的个数。(但让我先鸽一鸽

标签:暴力,分块,int,复杂度,sqrt,优雅,add,区间
From: https://www.cnblogs.com/George-Pig-Orz/p/FenKUAI.html

相关文章

  • Nginx实现浏览器端大文件分块上传
    ​PHP用超级全局变量数组$_FILES来记录文件上传相关信息的。1.file_uploads=on/off 是否允许通过http方式上传文件2.max_execution_time=30 允许脚本最大执行时间,超过这个时间就会报错3.memory_limit=50M 设置脚本可以分配的最大内存量,防止失控脚本占用过多内存,此指......
  • javascript实现浏览器端大文件分块上传
    ​ 以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传  可以带参数        [HttpPost("upload")]        publicJsonResultuploadProject(I......
  • js实现浏览器端大文件分块上传
    ​ 第一点:Java代码实现文件上传FormFilefile=manform.getFile();StringnewfileName= null;Stringnewpathname= null;StringfileAddre= "/numUp";try{    InputStreamstream=file.getInputStream();// 把文件读入    StringfilePath=request.......
  • vue实现浏览器端大文件分块上传
    ​ 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数下面直接贴代码吧,一些难懂的我大部分都加上注释了:上传文件实体类:看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。pub......
  • http实现浏览器端大文件分块上传
    ​ 前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传到一半中断后,继续上传却只能重头开始上传,这种让人不爽的体验。那有没有......
  • abc312e <暴力>
    题目E-TangencyofCuboids思路意识到本题的数据规模可以暴力去做!\(N=100\),\(N^3\)直接遍历整个空间可做;立方体间不相交,也就是可以直接遍历立方体中的所有点进行标记,不会超过整体空间体积;立方体不相交,也就是说同一个位置尽可能被标记一次;将空间中每个立方体所占点进行标......
  • antd的a-table选中复选框后,删除操作还仍然存在选中项的问题暴力解决法
    在antd的a-table中有复选框,选中后进行操作,比如删除,刷新后竟然还存在选中了的情况,这显然不合理,选中的参数是否清空或者拿到的就是选中的参数,都需要查看一边,查了一堆解决办法,试了一下,不行,不知道是不是vue3的情况就不行。网络中的方案大多都是:<a-tablebordered:......
  • 分块-优雅的暴力
    前言某人:线段树好难,学不会,树状数组感觉用途少好多,怎么办啊Ben:入我分块神教!ps:作者不认为分块是数据结构,而是一种思想。本文代码来自作者不同时期,马蜂习惯存在差别前置芝士:循环,数组,没了一序列分块对于给定序列要求增删改查类问题,一般最常用线段树和BIT,毕竟是高贵的log但......
  • Java 如何优雅的计算代码块耗时
    Java如何优雅地计算代码块耗时在开发过程中,有时我们需要对某个代码块的执行时间进行计算,以便了解其性能和优化的空间。本文将介绍一种优雅的方法来计算Java代码块的耗时,使用System.nanoTime()方法来获取准确的时间戳,并结合try-finally语句来确保计时器的正确使用。System.......
  • Sa-Token简单几行代码,优雅的实现 SpringBoot 鉴权
    一、添加依赖二、设置配置文件三、创建测试Controller:登录接口四、创建测试Controller:普通访问接口五、检验当前会话是否已经登录六、路由拦截鉴权七、自定义全局异常拦截添加依赖<dependency><groupId>cn.dev33</groupId><artifactId>......