Argestes 和序列问题

一 问题描述

Argestes 有很多爱好,特别喜欢解决查询问题。有一天,Argestes 想出了这样的问题。给出一个由 N 个非负整数组成的序列,a [1],a [2],a[3] ... a [n]。然后对序列进行 M 个操作。操作可以是以下之一。

S X Y:你应该将 a[x] 的值设置为 y (换句话说,执行赋值 a[x] = y)。

Q L R D P:区间[L, R]有多少个数字的第 D 位是 P, L 和 R 之间是序列的索引。

注意:数字的第1位是最低有效位。

二 输入和输出

1 输入

第一行有一个整数 T,表示测试用例的数量。

对于每个测试用例,第一行包含两个数字 N 和 M。第二行包含 N 个整数,由空格分隔:a[1],a[2]...a[n]。数组元素的初始值。接下来的 M 行中的每一行都以字符类型开头。

如果 type==S,则行中将有两个整数:X,Y

如果 type==Q,则行中将有四个整数:L R D P

2 输出

对于每个操作 Q,输出一行包含答案。

三 输入和输出样例

1 输入样例

1

5 7

10 11 12 13 14

Q 1 5 2 1

Q 1 5 1 0

Q 1 5 1 1

Q 1 5 3 0

Q 1 5 3 1

S 1 100

Q 1 5 3 1

2 输出样例

5

1

1

5

0

1

四 分析和设计

1 分析

本问题包括点更新和区间查询。区间查询比较特殊,查询第 D 位是 P 的数有多少个。可以采用分块的方法解决。

2 设计

a 分块

划分块,统计每一块每一位上的数有多少个。block[j][j][k]表示第 i 块中第 j 位上是 k 的数有多少个。

b 查询

查询区间 [l,r] 有多少个数字的第 d 位是 p。

如果该区间属于同一块,则暴力累加块内第 d 位是 p 的数有多少个。

如果该区间包含多块,则累加中间每一块 i 的 block[i][d][p],然后暴力累加左端和右端第 d 位是 p 的数有多少个。

c 更新

将 a[x] 的值更新为y。因为原来 x 所属块己统计了 a[x] 每一位上数的个数,此时需要减去;然后再将新的值 y 累加上即可。

五 代码

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

import static java.lang.Math.sqrt;

public class Hdu5057 {
    private int maxn = 100010;
    int a[] = new int[maxn];
    int belong[] = new int[maxn];
    int L[] = new int[maxn];
    int R[] = new int[maxn];
    // block[i][j][k] 表示第 i 块中第 j 位上是 k 的数有多少个
    int block[][][] = new int[400][12][12];
    int n;
    int m;

    int ten[] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};

    void build() {
        int t = (int) sqrt(n);
        int num = n / t;
        if (n % t != 0) num++;
        for (int i = 1; i <= num; i++) {
            // 每块的左右位置,从 1 开始
            L[i] = (i - 1) * t + 1;
            R[i] = i * t;
        }
        R[num] = n;
        for (int i = 1; i <= n; i++)
            belong[i] = (i - 1) / t + 1; // 所属块,从 1 开始
        for (int i = 1; i <= n; i++) {
            int temp = a[i];
            for (int j = 1; j <= 10; j++) { // 位数最多有 10 位 1<=D<=10
                block[belong[i]][j][temp % 10]++; // 块,位,位上的数
                temp /= 10;
            }
        }
    }

    int query(int l, int r, int d, int p) {
        int ans = 0;
        if (belong[l] == belong[r]) { // 属于同一块
            for (int i = l; i <= r; i++) // 暴力统计
                if ((a[i] / ten[d]) % 10 == p)
                    ans++;
            return ans;
        }
        for (int i = belong[l] + 1; i < belong[r]; i++) // 累加中间块
            ans += block[i][d][p];
        for (int i = l; i <= R[belong[l]]; i++) { // 左端暴力累加
            if ((a[i] / ten[d]) % 10 == p)
                ans++;
        }
        for (int i = L[belong[r]]; i <= r; i++) { // 右端暴力累加
            if ((a[i] / ten[d]) % 10 == p)
                ans++;
        }
        return ans;
    }

    void update(int x, int y) {
        for (int i = 1; i <= 10; i++) { // 原来的统计数减少
            block[belong[x]][i][a[x] % 10]--;
            a[x] /= 10;
        }
        a[x] = y;
        for (int i = 1; i <= 10; i++) { // 新的统计数增加
            block[belong[x]][i][y % 10]++;
            y /= 10;
        }
    }

    public String output = "";

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

        String[] key = line[1].split(" ");

        for (int i = 1; i <= n; i++) {
            a[i] = Integer.parseInt(key[i - 1]);
        }
        build(); // 划分块
        int j = 0;
        while (m-- > 0) {
            String[] command = line[j + 2].split(" ");
            j++;
            if (command[0].charAt(0) == 'S') {
                int x, y;
                x = Integer.parseInt(command[1]);
                y = Integer.parseInt(command[2]);
                update(x, y);
            } else {
                int l, r, d, p;
                l = Integer.parseInt(command[1]);
                r = Integer.parseInt(command[2]);
                d = Integer.parseInt(command[3]);
                p = Integer.parseInt(command[4]);
                output += query(l, r, d, p) + "\n";
            }
        }
        return output;
    }
}

六 测试

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值