• 分块[update,可以和原本的yxc板子对比]


    分块

    我发现,要学莫队,分块还是得学好。上一次写的分块的博客太简单,这一篇补一下。

    分块是一种思想,把一个整体划分为若干个小块,对整块进行整体处理,对零散块进行单独处理。本文主要介绍块状数组,利用分块思想处理区间问题的一种数据结构。

    块状数组将一个长度为 (N) 的数组划分为 (sqrt{N}) 块,这样,对于任意的查询区间 ([l,r]),我们可以将其拆分为最多不超过 (sqrt{N}) 块和最多不超过 (sqrt{N}) 个零散区间。总体复杂度为 (O(sqrt{N}))

    显然,分块的时间复杂度比不上线段树BIT这些对数级算法,但由此换来的,是更高的灵活性。与线段树不同,块状数组支持维护不满足结合律的信息,也不需要一层层传递标记。举一个例子,我们需要查询任意区间 ([l,r]) 的重复次数不少于 (3) 的数字个数,如果用线段树维护,无法用一个时间复杂度少于为 (O(N)) 的函数 (f) 来求解 (ans[l,r] = f(ans[l,mid], ans[mid+1,r]))

    因此我们需要引入块状数组。事实上,块状数组也可以看作一棵高度为 (3) 的树。

    截屏2021-04-29 上午8.26.42

    1. 预处理 (O(N))

    具体地,使用块状数组,我们需要划定每一个块所占据的范围、每个数的分块编号。

    const int N = 1e6+5;
    const int sqN = 1e3+5;
    
    int n, sq;
    int st[sqN]; // st[i] 表示i号块的第一个元素的下标
    int ed[sqN]; // ed[i] 表示i号块的最后一个元素的下标
    int id[N]; 	 // id[i] 表示元素i的分块编号
    
    // 维护信息
    int arr[N]; 	// 零散数组
    int sum[N];		// 分块和数组
    int mark[N];	// 分块公共加数组
    
    void init(){
      	sq = sqrt(n);
      	for(int i = 1; i <= n; ++i){
          	st[i] = n / sq * (i-1) + 1;
          	ed[i] = n / sq * i;
        }
      	ed[sq] = n;
      	
      	for(int i = 1; i <= sq; ++i){
          	for(int j = st[i]; j <= ed[i]; ++j){
              	id[j] = i;
            }
        }
    }
    

    2. 修改 (O(sqrt{N}))

    对于待修改区间 ([l, r]),当 (l)(r) 在同一块时,直接暴力修改零散数组sum数组。如果不在同一块,则先处理 ([l, ed[id[l]]])([st[id[r]], r])这些零散点,然后再处理 ([id[l]+1, id[r]-1]) 这些分块。

    但是应该注意一点是:只有在修改零散点时去修改零散数组和sum数组。如果对于整个分块操作,应该只修改mark数组而不修改sum数组。这一点很重要,不要在操作整个分块的时候,令sum[i] += size[i] * x,否则就会重复计算

    void modify(int l, int r, int x){
      	if(id[l] == id[r]){
          	for(int i = l; i <= r; ++i){
              	arr[i] += x;
            }
          	sum[id[l]] += (r-l+1) * x;
        }else{
          	for(int i = l; i <= ed[id[l]]; ++i){
              	arr[i] += x;
            }
          	sum[id[l]] += (id[l]-l+1) * x;
          	
          	for(int i = st[id[r]]; i <= r; ++i){
              	arr[i] += x;
            }
          	sum[id[r]] += (r-id[r]+1) * x;
          
          	for(int i = id[l]+1; i < id[r]; ++i){
              	mark[i] += x;
            }
        }
    }
    

    3. 查询 (O(sqrt{N}))

    查询和修改的基本思路相同,注意一点,无论是操作零散点还是整个分块,都需要加上mark数组。

    int query(int l, int r){
      	int ans = 0;
    		if(l == r){
          	for(int i = l; i <= r; ++i){
    						ans += arr[i];
            }
          	ans += mark[id[i]] * (r-l+1);
        }else{
          	for(int i = l; i <= ed[id[l]]; ++i){
              	ans += arr[i];
            }
          	ans += mark[id[l]] * (ed[id[l]]-l+1);
    
          	for(int i = st[id[r]]; i <= r; ++i){
    						ans += arr[i];
            }
         		ans += mark[id[r]] * (r-st[id[r]]+1);
          	
          	for(int i = id[l]+1; i < id[r]; ++i){
              	ans += sum[i] + mark[i] * (ed[i]-st[i]+1);
            }
        }
      	return ans;
    }
    
    ---- suffer now and live the rest of your life as a champion ----
  • 相关阅读:
    python接口测试(二)——配置文件的使用
    python接口测试(一)——http请求及token获取
    jmeter接口测试--响应结果Unicode转码成中文
    jmeter接口测试--获取token
    jmeter接口测试--参数化
    Sysbench安装步骤及详情
    云数据库安装及使用
    npm start 和 npm run start的关系
    使用JS控制伪元素的几种方法
    html5新属性
  • 原文地址:https://www.cnblogs.com/popodynasty/p/14721259.html
Copyright © 2020-2023  润新知