跳转至

3145. 大数组元素的乘积

题目描述

一个非负整数 x 的 强数组 指的是满足元素为 2 的幂且元素总和为 x 的最短有序数组。下表说明了如何确定 强数组 的示例。可以证明,x 对应的强数组是独一无二的。

数字 二进制表示 强数组
1 00001 [1]
8 01000 [8]
10 01010 [2, 8]
13 01101 [1, 4, 8]
23 10111 [1, 2, 4, 16]

 

我们将每一个升序的正整数 i (即1,2,3等等)的 强数组 连接得到数组 big_nums ,big_nums 开始部分为 [1, 2, 1, 2, 4, 1, 4, 2, 4, 1, 2, 4, 8, ...] 。

给你一个二维整数数组 queries ,其中 queries[i] = [fromi, toi, modi] ,你需要计算 (big_nums[fromi] * big_nums[fromi + 1] * ... * big_nums[toi]) % modi 。

请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

 

示例 1:

输入:queries = [[1,3,7]]

输出:[4]

解释:

只有一个查询。

big_nums[1..3] = [2,1,2] 。它们的乘积为 4。结果为 4 % 7 = 4

示例 2:

输入:queries = [[2,5,3],[7,7,4]]

输出:[2,2]

解释:

有两个查询。

第一个查询:big_nums[2..5] = [1,2,4,1] 。它们的乘积为 8 。结果为  8 % 3 = 2

第二个查询:big_nums[7] = 2 。结果为 2 % 4 = 2

 

提示:

  • 1 <= queries.length <= 500
  • queries[i].length == 3
  • 0 <= queries[i][0] <= queries[i][1] <= 1015
  • 1 <= queries[i][2] <= 105

 

解法

方法一:二分查找 + 位运算

连续的正整数数字对应的强整数数组连接得到数组 $\textit{bignums}$,题目需要我们求出对于每个查询 $[\textit{left}, \textit{right}, \textit{mod}]$,子数组 $\textit{bignums}[\textit{left}..\textit{right}]$ 的乘积对 $\textit{mod}$ 取模的结果。由于子数组每个元素都是 $2$ 的幂,这等价于求子数组的幂次之和 $\textit{power}$,然后计算 $2^{\textit{power}} \bmod \textit{mod}$。例如,对于子数组 $[1, 4, 8]$,即 $[2^0, 2^2, 2^3]$,其幂次之和为 $0 + 2 + 3 = 5$,所以 $2^5 \bmod \textit{mod}$ 就是我们要求的结果。

因此,我们不妨将 $\textit{bignums}$ 转换为幂次数组,即对于子数组 $[1, 4, 8]$,我们将其转换为 $[0, 2, 3]$。这样,问题转换为求幂次数组的子数组之和,即 $\textit{power} = \textit{f}(\textit{right} + 1) - \textit{f}(\textit{left})$,其中 $\textit{f}(i)$ 表示 $\textit{bignums}[0..i)$ 的幂次之和,也即是前缀和。

接下来,就是根据下标 $i$ 计算 $\textit{f}(i)$ 的值。我们可以使用二分查找的方法,先找到强数组长度和小于 $i$ 的最大数字,然后再计算剩下的数字的幂次之和。

我们根据题目描述,列出数字 $0..14$ 的强整数:

$\textit{nums}$ 8($2^3$) 4($2^2$) 2($2^1$) ($2^0$)
0 0 0 0 0
1 0 0 0 1
2 0 0 1 0
3 0 0 1 1
4 0 1 0 0
5 0 1 0 1
6 0 1 1 0
7 0 1 1 1
8 1 0 0 0
9 1 0 0 1
10 1 0 1 0
11 1 0 1 1
12 1 1 0 0
13 1 1 0 1
14 1 1 1 0

将数字按照 $[2^i, 2^{i+1}-1]$ 的区间划分为不同的颜色,可以发现,区间 $[2^i, 2^{i+1}-1]$ 的数字,相当于在区间 $[0, 2^i-1]$ 的数字基础上,每个数字加上 $2^i$。我们可以根据这个规律,计算出 $\textit{bignums}$ 的前 $i$ 组的所有数字的强数组个数之和 $\textit{cnt}[i]$ 和幂次之和 $\textit{s}[i]$。

接下来,对于任何数字,我们考虑如何计算其强数组的个数和幂次之和。我们可以通过二进制的方式,从最高位开始,诸位计算。例如,对于数字 $13 = 2^3 + 2^2 + 2^0$,前 $2^3$ 个数字的结果可以由 $textit{cnt}[3]$ 和 $\textit{s}[3]$ 计算得到,而剩下的 $[2^3, 13]$ 的结果,相当于给 $[0, 13-2^3]$ 的所有数字,即 $[0, 5]$ 的所有数字的强数组增加 $3$,问题转换为计算 $[0, 5]$ 的所有数字的强数组的个数和幂次之和。这样,我们可以计算出任意数字的强数组的个数和幂次之和。

最后,我们可以根据 $\textit{power}$ 的值,利用快速幂的方法,计算出 $2^{\textit{power}} \bmod \textit{mod}$ 的结果。

时间复杂度 $O(q \times \log M)$,空间复杂度 $(\log M)$。其中 $q$ 为查询的个数,而 $M$ 为数字的上界,本题中 $M \le 10^{15}$。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
m = 50
cnt = [0] * (m + 1)
s = [0] * (m + 1)
p = 1
for i in range(1, m + 1):
    cnt[i] = cnt[i - 1] * 2 + p
    s[i] = s[i - 1] * 2 + p * (i - 1)
    p *= 2


def num_idx_and_sum(x: int) -> tuple:
    idx = 0
    total_sum = 0
    while x:
        i = x.bit_length() - 1
        idx += cnt[i]
        total_sum += s[i]
        x -= 1 << i
        total_sum += (x + 1) * i
        idx += x + 1
    return (idx, total_sum)


def f(i: int) -> int:
    l, r = 0, 1 << m
    while l < r:
        mid = (l + r + 1) >> 1
        idx, _ = num_idx_and_sum(mid)
        if idx < i:
            l = mid
        else:
            r = mid - 1

    total_sum = 0
    idx, total_sum = num_idx_and_sum(l)
    i -= idx
    x = l + 1
    for _ in range(i):
        y = x & -x
        total_sum += y.bit_length() - 1
        x -= y
    return total_sum


class Solution:
    def findProductsOfElements(self, queries: List[List[int]]) -> List[int]:
        return [pow(2, f(right + 1) - f(left), mod) for left, right, mod in queries]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Solution {
    private static final int M = 50;
    private static final long[] cnt = new long[M + 1];
    private static final long[] s = new long[M + 1];

    static {
        long p = 1;
        for (int i = 1; i <= M; i++) {
            cnt[i] = cnt[i - 1] * 2 + p;
            s[i] = s[i - 1] * 2 + p * (i - 1);
            p *= 2;
        }
    }

    private static long[] numIdxAndSum(long x) {
        long idx = 0;
        long totalSum = 0;
        while (x > 0) {
            int i = Long.SIZE - Long.numberOfLeadingZeros(x) - 1;
            idx += cnt[i];
            totalSum += s[i];
            x -= 1L << i;
            totalSum += (x + 1) * i;
            idx += x + 1;
        }
        return new long[] {idx, totalSum};
    }

    private static long f(long i) {
        long l = 0;
        long r = 1L << M;
        while (l < r) {
            long mid = (l + r + 1) >> 1;
            long[] idxAndSum = numIdxAndSum(mid);
            long idx = idxAndSum[0];
            if (idx < i) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }

        long[] idxAndSum = numIdxAndSum(l);
        long totalSum = idxAndSum[1];
        long idx = idxAndSum[0];
        i -= idx;
        long x = l + 1;
        for (int j = 0; j < i; j++) {
            long y = x & -x;
            totalSum += Long.numberOfTrailingZeros(y);
            x -= y;
        }
        return totalSum;
    }

    public int[] findProductsOfElements(long[][] queries) {
        int n = queries.length;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            long left = queries[i][0];
            long right = queries[i][1];
            long mod = queries[i][2];
            long power = f(right + 1) - f(left);
            ans[i] = qpow(2, power, mod);
        }
        return ans;
    }

    private int qpow(long a, long n, long mod) {
        long ans = 1 % mod;
        for (; n > 0; n >>= 1) {
            if ((n & 1) == 1) {
                ans = ans * a % mod;
            }
            a = a * a % mod;
        }
        return (int) ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using ll = long long;
const int m = 50;
ll cnt[m + 1];
ll s[m + 1];
ll p = 1;

auto init = [] {
    cnt[0] = 0;
    s[0] = 0;
    for (int i = 1; i <= m; ++i) {
        cnt[i] = cnt[i - 1] * 2 + p;
        s[i] = s[i - 1] * 2 + p * (i - 1);
        p *= 2;
    }
    return 0;
}();

pair<ll, ll> numIdxAndSum(ll x) {
    ll idx = 0;
    ll totalSum = 0;
    while (x > 0) {
        int i = 63 - __builtin_clzll(x);
        idx += cnt[i];
        totalSum += s[i];
        x -= 1LL << i;
        totalSum += (x + 1) * i;
        idx += x + 1;
    }
    return make_pair(idx, totalSum);
}

ll f(ll i) {
    ll l = 0;
    ll r = 1LL << m;
    while (l < r) {
        ll mid = (l + r + 1) >> 1;
        auto idxAndSum = numIdxAndSum(mid);
        ll idx = idxAndSum.first;
        if (idx < i) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }

    auto idxAndSum = numIdxAndSum(l);
    ll totalSum = idxAndSum.second;
    ll idx = idxAndSum.first;
    i -= idx;
    ll x = l + 1;
    for (int j = 0; j < i; ++j) {
        ll y = x & -x;
        totalSum += __builtin_ctzll(y);
        x -= y;
    }
    return totalSum;
}

ll qpow(ll a, ll n, ll mod) {
    ll ans = 1 % mod;
    a = a % mod;
    while (n > 0) {
        if (n & 1) {
            ans = ans * a % mod;
        }
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}

class Solution {
public:
    vector<int> findProductsOfElements(vector<vector<ll>>& queries) {
        int n = queries.size();
        vector<int> ans(n);
        for (int i = 0; i < n; ++i) {
            ll left = queries[i][0];
            ll right = queries[i][1];
            ll mod = queries[i][2];
            ll power = f(right + 1) - f(left);
            if (power < 0) {
                power += mod;
            }
            ans[i] = static_cast<int>(qpow(2, power, mod));
        }
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
const m = 50

var cnt [m + 1]int64
var s [m + 1]int64
var p int64 = 1

func init() {
    cnt[0] = 0
    s[0] = 0
    for i := 1; i <= m; i++ {
        cnt[i] = cnt[i-1]*2 + p
        s[i] = s[i-1]*2 + p*(int64(i)-1)
        p *= 2
    }
}

func numIdxAndSum(x int64) (int64, int64) {
    var idx, totalSum int64
    for x > 0 {
        i := 63 - bits.LeadingZeros64(uint64(x))
        idx += cnt[i]
        totalSum += s[i]
        x -= 1 << i
        totalSum += (x + 1) * int64(i)
        idx += x + 1
    }
    return idx, totalSum
}

func f(i int64) int64 {
    l, r := int64(0), int64(1)<<m
    for l < r {
        mid := (l + r + 1) >> 1
        idx, _ := numIdxAndSum(mid)
        if idx < i {
            l = mid
        } else {
            r = mid - 1
        }
    }

    _, totalSum := numIdxAndSum(l)
    idx, _ := numIdxAndSum(l)
    i -= idx
    x := l + 1
    for j := int64(0); j < i; j++ {
        y := x & -x
        totalSum += int64(bits.TrailingZeros64(uint64(y)))
        x -= y
    }
    return totalSum
}

func qpow(a, n, mod int64) int64 {
    ans := int64(1) % mod
    a = a % mod
    for n > 0 {
        if n&1 == 1 {
            ans = (ans * a) % mod
        }
        a = (a * a) % mod
        n >>= 1
    }
    return ans
}

func findProductsOfElements(queries [][]int64) []int {
    ans := make([]int, len(queries))
    for i, q := range queries {
        left, right, mod := q[0], q[1], q[2]
        power := f(right+1) - f(left)
        ans[i] = int(qpow(2, power, mod))
    }
    return ans
}

评论