分块算法解决简单的整数问题

一 问题描述

有 N 个数,A1 A2 An,需要对其进行两种操作,一种操作是对给定区域中的每个数据都添加一个给定的数,另一个操作是查询给定区间中数的总和。

二 输入和输出

1 输入

第 1 行包含两个数 N 和 Q,第 2 行,包括 N 个数,为 A1 A2 An 的初始值。接下来的 Q 行,每行都表示一种操作。

C a b c :将 Aa Aa+1 ... Ab 中的每一个数都加 c 

Q a b:查询 Aa Aa+1 ... Ab 的总和

2 输出

对于每个查询都单行输出区间和的值。

三 输入和输出样例

1 输入样例

10 5

1 2 3 4 5 6 7 8 9 10

Q 4 4

Q 1 10

Q 2 4

C 3 6 3

Q 2 4

2 输出样例

4

55

9

15

四 分析和设计

1 分析

本问题有两种操作:区间更新和区间查询,可采用分块算法解决。

2 算法设计

a 分块预处理

将序列分块,然后对每个块都标记左右端点 L[i] 和 R[i],对最后一块需要特殊处理;标记每个元素所属的块,累加每一块的和值。

b 区间更新

首先取 l 和 r 所属的块,p=pos[l],q=pos[r];若属于同一块,则对该区间的所有元素都进行暴力修改,同时更新该块的和值。若不属于同一块,则对中间完全覆盖的块打上懒标记,add[i]+=d;对首尾两端的元素暴力修改即可。

c 区间查询

首先取 l 和 r 所属的块,p=pos[l],q=pos[r];若属于同一块,则对该区间的所有元素都进行暴力累加,然后加上懒标记上的值。若不属于同一块,则对中间完全覆盖的块累加 sum[] 值和懒标记上的值,然后对首尾两端的元素暴力累加元素值及懒标记值。

五 代码

package com.platform.modules.alg.alglib.poj3468;

public class Poj3468 {
    public String output = "";
    private int N = 100010;
    private long a[] = new long[N];
    private long sum[] = new long[N];
    private long add[] = new long[N];
    private int L[] = new int[N];
    private int R[] = new int[N];
    private int d;
    private int pos[] = new int[N];
    int n;
    int m;
    int t;
    int l;
    int r;
    char op[] = new char[3];

    // 预处理
    void build() {
        t = (int) Math.sqrt(n * 1.0);
        int num = n / t;
        if (n % t != 0) num++;
        for (int i = 1; i <= num; i++) {
            // 每块的左右
            L[i] = (i - 1) * t + 1;
            R[i] = i * t;
        }
        R[num] = n;
        for (int i = 1; i <= num; i++)
            for (int j = L[i]; j <= R[i]; j++) {
                pos[j] = i; // 表示属于哪个块
                sum[i] += a[j]; // 计算每块和值
            }
    }

    // 区间[l,r]加上d
    void change(int l, int r, long d) {
        int p = pos[l], q = pos[r]; // 读所属块
        if (p == q) { // 在一块中
            for (int i = l; i <= r; i++) // 暴力修改
                a[i] += d;
            sum[p] += d * (r - l + 1); // 修改和值
        } else {
            for (int i = p + 1; i <= q - 1; i++) // 中间完全覆盖块打懒标记
                add[i] += d;
            for (int i = l; i <= R[p]; i++) // 左端暴力修改
                a[i] += d;
            sum[p] += d * (R[p] - l + 1);
            for (int i = L[q]; i <= r; i++) // 右端暴力修改
                a[i] += d;
            sum[q] += d * (r - L[q] + 1);
        }
    }

    // 区间查询
    long query(int l, int r) {
        int p = pos[l], q = pos[r];
        long ans = 0;
        if (p == q) { // 在一块中
            for (int i = l; i <= r; i++) // 累加
                ans += a[i];
            ans += add[p] * (r - l + 1); // 计算懒标记
        } else {
            for (int i = p + 1; i <= q - 1; i++) // 累加中间块
                ans += sum[i] + add[i] * (R[i] - L[i] + 1);
            for (int i = l; i <= R[p]; i++) // 左端暴力累加
                ans += a[i];
            ans += add[p] * (R[p] - l + 1);
            for (int i = L[q]; i <= r; i++) // 右端暴力累加
                ans += a[i];
            ans += add[q] * (r - L[q] + 1);
        }
        return ans;
    }

    public String cal(String input) {
        String[] line = input.split("\n");
        String[] nums = line[0].split(" ");
        n = Integer.parseInt(nums[0]);
        m = Integer.parseInt(nums[1]);
        String[] words = line[1].split(" ");


        for (int i = 1; i <= n; i++)
            a[i] = Integer.parseInt(words[i - 1]);
        build();
        for (int i = 1; i <= m; i++) {
            String[] command = line[i + 1].split(" ");
            l = Integer.parseInt(command[1]);
            r = Integer.parseInt(command[2]);


            if (command[0].charAt(0) == 'C') {
                d = Integer.parseInt(command[3]);
                change(l, r, d);
            } else
                output += query(l, r) + "\n";
        }
        return output;
    }
}

六 测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值