Find Sum of Array Product of Magical Sequences
Problem
You are given two integers, m and k, and an integer array nums.
A sequence of integers seq is called magical if:
seqhas a size ofm.0 <= seq[i] < nums.length- The binary representation of
2seq[0] + 2seq[1] + ... + 2seq[m - 1]haskset bits.
The array product of this sequence is defined as prod(seq) = (nums[seq[0]] * nums[seq[1]] * ... * nums[seq[m - 1]]).
Return the sum of the array products for all valid magical sequences.
Since the answer may be large, return it modulo 10^9 + 7.
A set bit refers to a bit in the binary representation of a number that has a value of 1.
Examples
Example 1
Input: m = 5, k = 5, nums = [1,10,100,10000,1000000]
Output: 991600007
Explanation:
All permutations of `[0, 1, 2, 3, 4]` are magical sequences, each with an
array product of 1013.
Example 2
Input: m = 2, k = 2, nums = [5,4,3,2,1]
Output: 170
Explanation:
The magical sequences are `[0, 1]`, `[0, 2]`, `[0, 3]`, `[0, 4]`, `[1, 0]`,
`[1, 2]`, `[1, 3]`, `[1, 4]`, `[2, 0]`, `[2, 1]`, `[2, 3]`, `[2, 4]`, `[3,
0]`, `[3, 1]`, `[3, 2]`, `[3, 4]`, `[4, 0]`, `[4, 1]`, `[4, 2]`, and `[4, 3]`.
Example 3
Input: m = 1, k = 1, nums = [28]
Output: 28
Explanation:
The only magical sequence is `[0]`.
Constraints
1 <= k <= m <= 301 <= nums.length <= 501 <= nums[i] <= 10^8
Solution
Method 1 - Naive brute-force enumeration (explanatory, no code)
Intuition
The most straightforward way is to generate every ordered sequence seq of length m where each element is an index in [0..n-1] (where n = nums.length), compute the integer S = sum(2^{seq[i]}) for that sequence, check if popcount(S) == k, and if so add the product nums[seq[0]] * ... * nums[seq[m-1]] to the running total (modulo 1e9+7). This is conceptually simple but quickly becomes infeasible because there are n^m ordered sequences.
Approach
- Recursively (or with m nested loops) enumerate every ordered sequence
seqof lengthm. At each recursion depth you pick an index in0..n-1. - Maintain the running product of chosen
numsvalues and the running integer sum of powers-of-two (S). When you reach depthm, computepopcount(S). If it equalsk, add the product to the answer moduloMOD. - Simple pruning: if at any point you detect that even with the best/worst possible remaining contributions you cannot reach
kset bits, you could skip (but in the naive approach this pruning is weak).
Why this is insufficient in practice
- The number of sequences is
n^m. With constraints up ton = 50andm = 30the naive method is astronomically large and unusable. - Even for moderate values (e.g., n=10, m=10) it's usually too slow.
Why memoization or combinatorics is needed (lead-in to Method 2)
- There is a great deal of repeated work in the naive enumeration: different orderings that share the same counts of each index produce the same product up to permutation multiplicity, and the binary addition behavior depends only on counts (and carries), not on order.
- Recasting the problem to iterate over counts of each index (a multiset view) and accounting for permutations with combinatorial coefficients reduces the search space dramatically. Likewise, memoizing states keyed by
(remaining, odd_needed, idx, carry)(or equivalent) collapses repeated subproblems.
We'll implement the memoized DP that uses these observations in Method 2, and then provide a bottom-up DP variant in Method 3.
Complexity
- ⏰ Time complexity:
O(n^m * m)– Enumerating every ordered sequence (n^m) and computing product/popcount for each sequence costs anotherO(m)per sequence; hence the naive upper bound isO(n^m * m). - 🧺 Space complexity:
O(m)– recursion stack andO(m)temporary workspace for product/bit-sum calculations.
Method 2 - Memoized DP (carry-by-binary-digit + combinatorics)
Intuition
We can avoid enumerating every ordered sequence by processing indices one-by-one and choosing how many times to include each index (0..remaining). The binary sum's behavior depends only on counts and carries per binary digit, and permutations of identical choices can be accounted for with combinatorics (binomial coefficients). Memoizing states keyed by (remaining, odd_needed, idx, carry) collapses repeated subproblems.
Approach
- Precompute combinatorics (either factorials/inverses or a Pascal table
C) up tom, and optionally precomputepow(nums[i], t, MOD)fort in 0..mto avoid repeated exponentiation. - Define a memoized recursion dfs(remaining, odd_needed, idx, carry) that returns the total sum of array-products modulo MOD for selecting exactly
remainingitems from indicesidx..n-1with currentcarryand still needingodd_neededset bits overall. - For the current
idx, iteratetakefrom0..remaining:ways = C[remaining][take]prod_contrib = nums[idx]^take % MOD- new_carry = carry + take
lsb = new_carry & 1(this bit contributes to the current bitcount)- recurse on
dfs(remaining - take, odd_needed - lsb, idx+1, new_carry >> 1)and multiply byways * prod_contrib.
- Sum these contributions modulo
MODand memoize the result.
State Definition
dfs(remaining, odd_needed, idx, carry)returns the total modular sum of products for selectingremainingmore elements from indicesidx..n-1with pendingcarryandodd_neededremaining set bits to form.
Recurrence Relation
ans = sum_{take=0..remaining} C(remaining, take) * nums[idx]^take * dfs(remaining-take, odd_needed - ((carry+take)&1), idx+1, (carry+take)>>1)
Code
C++
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const int64 MOD = 1000000007LL;
class Solution {
public:
int magicalSum(int m, int k, vector<int>& nums) {
int n = nums.size();
vector<vector<int64>> C(m + 1, vector<int64>(m + 1, 0));
for (int i = 0; i <= m; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
vector<vector<int64>> pw(n, vector<int64>(m + 1, 1));
for (int i = 0; i < n; ++i) for (int t = 1; t <= m; ++t) pw[i][t] = pw[i][t - 1] * nums[i] % MOD;
unordered_map<string, int> memo;
function<int(int,int,int,long long)> dfs = [&](int remaining, int oddNeeded, int idx, long long carry) -> int {
if (remaining < 0 || oddNeeded < 0) return 0;
if (remaining + __builtin_popcountll((unsigned long long)carry) < oddNeeded) return 0;
if (remaining == 0) return (oddNeeded == (int)__builtin_popcountll((unsigned long long)carry)) ? 1 : 0;
if (idx >= n) return 0;
string key = to_string(remaining) + ":" + to_string(oddNeeded) + ":" + to_string(idx) + ":" + to_string(carry);
if (memo.count(key)) return memo[key];
int64 res = 0;
for (int take = 0; take <= remaining; ++take) {
int64 ways = C[remaining][take];
int64 prod = pw[idx][take];
long long nc = carry + take;
int lsb = nc & 1LL;
int sub = dfs(remaining - take, oddNeeded - lsb, idx + 1, nc >> 1);
res = (res + ways % MOD * prod % MOD * sub) % MOD;
}
memo[key] = (int)res;
return memo[key];
};
return dfs(m, k, 0, 0);
}
};
Go
package main
import (
"fmt"
"strconv"
)
const MOD = 1000000007
func magicalSum(m int, k int, nums []int) int {
n := len(nums)
C := make([][]int, m+1)
for i := range C {
C[i] = make([]int, m+1)
C[i][0] = 1
for j := 1; j <= i; j++ {
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD
}
}
pw := make([][]int, n)
for i := 0; i < n; i++ {
pw[i] = make([]int, m+1)
pw[i][0] = 1
for t := 1; t <= m; t++ {
pw[i][t] = int((int64(pw[i][t-1]) * int64(nums[i])) % MOD)
}
}
memo := make(map[string]int)
var dfs func(remaining, oddNeeded, idx int, carry int) int
dfs = func(remaining, oddNeeded, idx int, carry int) int {
if remaining < 0 || oddNeeded < 0 {
return 0
}
if remaining + bitsSet(carry) < oddNeeded {
return 0
}
if remaining == 0 {
if oddNeeded == bitsSet(carry) {
return 1
}
return 0
}
if idx >= n {
return 0
}
key := strconv.Itoa(remaining) + ":" + strconv.Itoa(oddNeeded) + ":" + strconv.Itoa(idx) + ":" + strconv.Itoa(carry)
if v, ok := memo[key]; ok { return v }
res := 0
for take := 0; take <= remaining; take++ {
ways := C[remaining][take]
prod := pw[idx][take]
nc := carry + take
lsb := nc & 1
sub := dfs(remaining - take, oddNeeded - lsb, idx+1, nc>>1)
res = (res + int((int64(ways) * int64(prod) % MOD) * int64(sub) % MOD)) % MOD
}
memo[key] = res
return res
}
return dfs(m, k, 0, 0)
}
func bitsSet(x int) int {
cnt := 0
for x > 0 {
cnt += x & 1
x >>= 1
}
return cnt
}
func main(){
// placeholder
fmt.Println(magicalSum(1,1,[]int{28}))
}
Java
class Solution {
private static final int MOD = 1_000_000_007;
public int magicalSum(int m, int k, int[] nums) {
int n = nums.length;
long[][] C = new long[m + 1][m + 1];
for (int i = 0; i <= m; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
}
long[][] pw = new long[n][m + 1];
for (int i = 0; i < n; i++) {
pw[i][0] = 1;
for (int t = 1; t <= m; t++) {
pw[i][t] = pw[i][t - 1] * nums[i] % MOD;
}
}
Map<String, Integer> memo = new HashMap<>();
return dfs(m, k, 0, 0L, nums, C, pw, memo);
}
private int dfs(int remaining, int oddNeeded, int idx, long carry, int[] nums, long[][] C, long[][] pw, Map<String, Integer> memo) {
if (remaining < 0 || oddNeeded < 0) return 0;
if (remaining + Long.bitCount(carry) < oddNeeded) return 0;
if (remaining == 0) return (oddNeeded == Long.bitCount(carry)) ? 1 : 0;
if (idx >= nums.length) return 0;
String key = remaining + ":" + oddNeeded + ":" + idx + ":" + carry;
if (memo.containsKey(key)) return memo.get(key);
long res = 0;
for (int take = 0; take <= remaining; ++take) {
long ways = C[remaining][take];
long prod = pw[idx][take];
long nc = carry + take;
int lsb = (int) (nc & 1L);
int sub = dfs(remaining - take, oddNeeded - lsb, idx + 1, nc >> 1, nums, C, pw, memo);
res = (res + ways % MOD * prod % MOD * sub) % MOD;
}
memo.put(key, (int) res);
return (int) res;
}
}
Kotlin
class Solution {
private val MOD = 1000000007L
fun magicalSum(m: Int, k: Int, nums: IntArray): Int {
val n = nums.size
val C = Array(m + 1) { LongArray(m + 1) }
for (i in 0..m) {
C[i][0] = 1L
for (j in 1..i) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD
}
val pw = Array(n) { LongArray(m + 1) }
for (i in 0 until n) {
pw[i][0] = 1L
for (t in 1..m) pw[i][t] = pw[i][t - 1] * nums[i] % MOD
}
val memo = HashMap<String, Int>()
fun dfs(remaining: Int, oddNeeded: Int, idx: Int, carry: Long): Int {
if (remaining < 0 || oddNeeded < 0) return 0
if (remaining + java.lang.Long.bitCount(carry) < oddNeeded) return 0
if (remaining == 0) return if (oddNeeded == java.lang.Long.bitCount(carry)) 1 else 0
if (idx >= n) return 0
val key = "$remaining:$oddNeeded:$idx:$carry"
if (memo.containsKey(key)) return memo[key]!!
var res = 0L
for (take in 0..remaining) {
val ways = C[remaining][take]
val prod = pw[idx][take]
val nc = carry + take
val lsb = (nc and 1L).toInt()
val sub = dfs(remaining - take, oddNeeded - lsb, idx + 1, nc shr 1)
res = (res + ways % MOD * prod % MOD * sub) % MOD
}
memo[key] = res.toInt()
return res.toInt()
}
return dfs(m, k, 0, 0L)
}
}
Python
from functools import lru_cache
from typing import List
import math
class Solution:
def magicalSum(self, m: int, k: int, nums: List[int]) -> int:
MOD = 10**9 + 7
n = len(nums)
@lru_cache(None)
def dfs(remaining: int, odd_needed: int, index: int, carry: int) -> int:
if remaining < 0 or odd_needed < 0:
return 0
if remaining + carry.bit_count() < odd_needed:
return 0
if remaining == 0:
return 1 if odd_needed == carry.bit_count() else 0
if index >= n:
return 0
ans = 0
for take in range(remaining + 1):
ways = math.comb(remaining, take) * pow(nums[index], take, MOD) % MOD
new_carry = carry + take
ans = (ans + ways * dfs(remaining - take, odd_needed - (new_carry & 1), index + 1, new_carry >> 1)) % MOD
return ans
return dfs(m, k, 0, 0)
Rust
use std::collections::HashMap;
const MOD: i64 = 1_000_000_007;
pub struct Solution;
impl Solution {
pub fn magical_sum(m: i32, k: i32, nums: Vec<i32>) -> i32 {
let n = nums.len();
let m_usize = m as usize;
// comb table
let mut c = vec![vec![0i64; m_usize + 1]; m_usize + 1];
for i in 0..=m_usize {
c[i][0] = 1;
for j in 1..=i {
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % MOD;
}
}
// pow table
let mut pw = vec![vec![1i64; m_usize + 1]; n];
for i in 0..n {
for t in 1..=m_usize {
pw[i][t] = pw[i][t - 1] * nums[i] as i64 % MOD;
}
}
let mut memo: HashMap<String, i64> = HashMap::new();
fn popcount64(x: i64) -> i32 { x.count_ones() as i32 }
fn dfs(remaining: i32, odd_needed: i32, idx: usize, carry: i64, n: usize, m_usize: usize, c: &Vec<Vec<i64>>, pw: &Vec<Vec<i64>>, memo: &mut HashMap<String, i64>) -> i64 {
if remaining < 0 || odd_needed < 0 { return 0; }
if remaining as usize + popcount64(carry) as usize < odd_needed as usize { return 0; }
if remaining == 0 { return if odd_needed == popcount64(carry) { 1 } else { 0 }; }
if idx >= n { return 0; }
let key = format!("{}:{}:{}:{}", remaining, odd_needed, idx, carry);
if let Some(&v) = memo.get(&key) { return v; }
let mut res = 0i64;
for take in 0..=remaining as usize {
let ways = c[remaining as usize][take] % MOD;
let prod = pw[idx][take] % MOD;
let nc = carry + take as i64;
let lsb = (nc & 1) as i32;
let sub = dfs(remaining - take as i32, odd_needed - lsb, idx + 1, nc >> 1, n, m_usize, c, pw, memo);
res = (res + ways % MOD * prod % MOD * sub % MOD) % MOD;
}
memo.insert(key, res);
res
}
dfs(m, k, 0, 0, n, m_usize, &c, &pw, &mut memo) as i32
}
}
TypeScript
class Solution {
magicalSum(m: number, k: number, nums: number[]): number {
const MOD = 1_000_000_007;
const n = nums.length;
const C: number[][] = Array.from({length: m+1}, () => new Array(m+1).fill(0));
for (let i = 0; i <= m; ++i) {
C[i][0] = 1;
for (let j = 1; j <= i; ++j) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
const pw: number[][] = Array.from({length: n}, () => new Array(m+1).fill(1));
for (let i = 0; i < n; ++i) for (let t = 1; t <= m; ++t) pw[i][t] = Math.floor(pw[i][t-1] * nums[i] % MOD);
const memo = new Map<string, number>();
function dfs(remaining: number, oddNeeded: number, idx: number, carry: number): number {
if (remaining < 0 || oddNeeded < 0) return 0;
if (remaining + popcount(carry) < oddNeeded) return 0;
if (remaining === 0) return oddNeeded === popcount(carry) ? 1 : 0;
if (idx >= n) return 0;
const key = `${remaining}:${oddNeeded}:${idx}:${carry}`;
if (memo.has(key)) return memo.get(key)!;
let res = 0;
for (let take = 0; take <= remaining; ++take) {
const ways = C[remaining][take];
const prod = pw[idx][take];
const nc = carry + take;
const lsb = nc & 1;
const sub = dfs(remaining - take, oddNeeded - lsb, idx + 1, nc >> 1);
res = (res + (((ways * prod) % MOD) * sub) % MOD) % MOD;
}
memo.set(key, res);
return res;
}
function popcount(x: number): number { return x.toString(2).split('1').length - 1; }
return dfs(m, k, 0, 0);
}
}
Notes for other languages
- C++, Go, Kotlin, Rust and TypeScript follow the same recursion and memoization pattern. Use packed integer keys or strings for memoization keys; precompute
Candpowtables for speed.
Complexity
- ⏰ Time complexity:
O(n * m * m * states)– each memo state iteratestakeup tom, and the number of distinct states is bounded byO(n * m * maxCarry)wheremaxCarryis O(m); overall polynomial innandmversus exponentialn^m. - 🧺 Space complexity:
O(n * m * maxCarry)– for memoization and recursion stack.
Notes: For production performance, pack the memo key into a fixed-size integer or use an iterative 4D DP table when memory allows (see Method 3 planned next).
Method 3 - Bottom-up DP (iterative 4D DP)
Intuition
We can convert the memoized recursion into an iterative DP that fills a 4D table: dp[pos][bits][carry][chosen] — the total sum of products (mod MOD) after processing the first pos indices, having accumulated bits set bits so far, with carry pending and having chosen chosen elements in total. Transitions enumerate how many copies cnt of the current index to take and update the next state's bits/carry/chosen accordingly while multiplying by combinatorial placement counts and power contributions.
Approach
- Precompute binomial coefficients
C[a][b]for0..mand precomputepw[i][t] = nums[i]^t % MODfort in 0..m. - Initialize
dp[0][0][0][0] = 1and iterateposfrom0ton-1. - For each reachable state
(bits, carry, chosen)atpos, iteratecntfrom0..remaining(remaining =m - chosen) and updatedp[pos+1][new_bits][new_carry][chosen+cnt] += dp[pos][bits][carry][chosen] * C[remaining][cnt] * pw[pos][cnt]. - After filling
dp[n], aggregate the answer by considering leftovercarryvalues: for eachcarry, computecarry_bits = popcount(carry)and adddp[n][k - carry_bits][carry][m]ifcarry_bits <= k.
Code
C++
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const int MOD = 1000000007;
class Solution {
public:
int magicalSum(int m, int k, vector<int>& nums) {
int n = nums.size();
// binomial table
vector<vector<int64>> C(m+1, vector<int64>(m+1, 0));
for (int i = 0; i <= m; ++i) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; ++j) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
// pow table
vector<vector<int64>> pw(n, vector<int64>(m+1, 1));
for (int i = 0; i < n; ++i) for (int t = 1; t <= m; ++t) pw[i][t] = pw[i][t-1] * nums[i] % MOD;
// dp[pos][bits][carry][chosen]
vector<vector<vector<vector<int64>>>> dp(
n+1,
vector<vector<vector<int64>>>(k+1, vector<vector<int64>>(m+1, vector<int64>(m+1, 0)))
);
dp[0][0][0][0] = 1;
for (int pos = 0; pos < n; ++pos) {
for (int bits = 0; bits <= k; ++bits) {
for (int carry = 0; carry <= m; ++carry) {
for (int chosen = 0; chosen <= m; ++chosen) {
int64 cur = dp[pos][bits][carry][chosen];
if (cur == 0) continue;
int remaining = m - chosen;
for (int cnt = 0; cnt <= remaining; ++cnt) {
int total = carry + cnt;
int new_bits = bits + (total & 1);
int new_carry = total >> 1;
if (new_bits <= k && new_carry <= m) {
int64 add = cur * C[remaining][cnt] % MOD * pw[pos][cnt] % MOD;
dp[pos+1][new_bits][new_carry][chosen+cnt] = (dp[pos+1][new_bits][new_carry][chosen+cnt] + add) % MOD;
}
}
}
}
}
}
int64 res = 0;
for (int carry = 0; carry <= m; ++carry) {
int carry_bits = __builtin_popcount(carry);
if (carry_bits <= k) {
res = (res + dp[n][k - carry_bits][carry][m]) % MOD;
}
}
return (int)res;
}
};
Go
package main
import (
"fmt"
)
const MOD = 1000000007
type Solution struct{}
func (s *Solution) magicalSum(m int, k int, nums []int) int {
n := len(nums)
C := make([][]int64, m+1)
for i := range C {
C[i] = make([]int64, m+1)
C[i][0] = 1
C[i][i] = 1
for j := 1; j < i; j++ {
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD
}
}
pw := make([][]int64, n)
for i := 0; i < n; i++ {
pw[i] = make([]int64, m+1)
pw[i][0] = 1
for t := 1; t <= m; t++ {
pw[i][t] = (pw[i][t-1] * int64(nums[i])) % MOD
}
}
// dp[pos][bits][carry][chosen]
dp := make([][][][]int64, n+1)
for pos := 0; pos <= n; pos++ {
dp[pos] = make([][][]int64, k+1)
for bits := 0; bits <= k; bits++ {
dp[pos][bits] = make([][]int64, m+1)
for carry := 0; carry <= m; carry++ {
dp[pos][bits][carry] = make([]int64, m+1)
}
}
}
dp[0][0][0][0] = 1
for pos := 0; pos < n; pos++ {
for bits := 0; bits <= k; bits++ {
for carry := 0; carry <= m; carry++ {
for chosen := 0; chosen <= m; chosen++ {
cur := dp[pos][bits][carry][chosen]
if cur == 0 { continue }
remaining := m - chosen
for cnt := 0; cnt <= remaining; cnt++ {
total := carry + cnt
newBits := bits + (total & 1)
newCarry := total >> 1
if newBits <= k && newCarry <= m {
add := cur * C[remaining][cnt] % MOD * pw[pos][cnt] % MOD
dp[pos+1][newBits][newCarry][chosen+cnt] = (dp[pos+1][newBits][newCarry][chosen+cnt] + add) % MOD
}
}
}
}
}
}
var res int64 = 0
for carry := 0; carry <= m; carry++ {
carryBits := bitsSet(carry)
if carryBits <= k {
res = (res + dp[n][k-carryBits][carry][m]) % MOD
}
}
return int(res)
}
func bitsSet(x int) int {
cnt := 0
for x > 0 {
cnt += x & 1
x >>= 1
}
return cnt
}
func main() {
s := &Solution{}
fmt.Println(s.magicalSum(1,1,[]int{28}))
}
Java
class Solution {
private static final int MOD = 1_000_000_007;
public int magicalSum(int m, int k, int[] nums) {
int n = nums.length;
long[][] C = new long[m+1][m+1];
for (int i = 0; i <= m; i++) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; j++) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
long[][] pw = new long[n][m+1];
for (int i = 0; i < n; i++) {
pw[i][0] = 1;
for (int t = 1; t <= m; t++) pw[i][t] = pw[i][t-1] * nums[i] % MOD;
}
long[][][][] dp = new long[n+1][k+1][m+1][m+1];
dp[0][0][0][0] = 1;
for (int pos = 0; pos < n; pos++) {
for (int bits = 0; bits <= k; bits++) {
for (int carry = 0; carry <= m; carry++) {
for (int chosen = 0; chosen <= m; chosen++) {
long cur = dp[pos][bits][carry][chosen];
if (cur == 0) continue;
int remaining = m - chosen;
for (int cnt = 0; cnt <= remaining; cnt++) {
int total = carry + cnt;
int newBits = bits + (total & 1);
int newCarry = total >> 1;
if (newBits <= k && newCarry <= m) {
long add = cur * C[remaining][cnt] % MOD * pw[pos][cnt] % MOD;
dp[pos+1][newBits][newCarry][chosen+cnt] = (dp[pos+1][newBits][newCarry][chosen+cnt] + add) % MOD;
}
}
}
}
}
}
long res = 0;
for (int carry = 0; carry <= m; carry++) {
int carryBits = Integer.bitCount(carry);
if (carryBits <= k) {
res = (res + dp[n][k - carryBits][carry][m]) % MOD;
}
}
return (int) res;
}
}
Kotlin
class Solution {
private val MOD = 1_000_000_007L
fun magicalSum(m: Int, k: Int, nums: IntArray): Int {
val n = nums.size
val C = Array(m + 1) { LongArray(m + 1) }
for (i in 0..m) {
C[i][0] = 1L
C[i][i] = 1L
for (j in 1 until i) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD
}
val pw = Array(n) { LongArray(m + 1) }
for (i in 0 until n) {
pw[i][0] = 1L
for (t in 1..m) pw[i][t] = pw[i][t-1] * nums[i] % MOD
}
// dp[pos][bits][carry][chosen]
val dp = Array(n+1) { Array(k+1) { Array(m+1) { LongArray(m+1) } } }
dp[0][0][0][0] = 1L
for (pos in 0 until n) {
for (bits in 0..k) {
for (carry in 0..m) {
for (chosen in 0..m) {
val cur = dp[pos][bits][carry][chosen]
if (cur == 0L) continue
val remaining = m - chosen
for (cnt in 0..remaining) {
val total = carry + cnt
val newBits = bits + (total and 1)
val newCarry = total shr 1
if (newBits <= k && newCarry <= m) {
val add = cur * C[remaining][cnt] % MOD * pw[pos][cnt] % MOD
dp[pos+1][newBits][newCarry][chosen+cnt] = (dp[pos+1][newBits][newCarry][chosen+cnt] + add) % MOD
}
}
}
}
}
}
var res = 0L
for (carry in 0..m) {
val carryBits = Integer.bitCount(carry)
if (carryBits <= k) res = (res + dp[n][k - carryBits][carry][m]) % MOD
}
return res.toInt()
}
}
Python
from typing import List
class Solution:
MOD = 10**9 + 7
def magicalSum(self, m: int, k: int, nums: List[int]) -> int:
n = len(nums)
# binomial table
C = [[0] * (m + 1) for _ in range(m + 1)]
for i in range(m + 1):
C[i][0] = 1
C[i][i] = 1
for j in range(1, i):
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % self.MOD
# pow table
pw = [[1] * (m + 1) for _ in range(n)]
for i in range(n):
for t in range(1, m + 1):
pw[i][t] = pw[i][t-1] * nums[i] % self.MOD
# dp[pos][bits][carry][chosen]
dp = [ [ [ [0] * (m + 1) for _ in range(m + 1)] for _ in range(k + 1)] for _ in range(n + 1) ]
dp[0][0][0][0] = 1
for pos in range(n):
for bits in range(k + 1):
for carry in range(m + 1):
for chosen in range(m + 1):
cur = dp[pos][bits][carry][chosen]
if cur == 0:
continue
remaining = m - chosen
for cnt in range(remaining + 1):
total = carry + cnt
new_bits = bits + (total & 1)
new_carry = total >> 1
if new_bits <= k and new_carry <= m:
add = cur * C[remaining][cnt] % self.MOD * pw[pos][cnt] % self.MOD
dp[pos+1][new_bits][new_carry][chosen+cnt] = (dp[pos+1][new_bits][new_carry][chosen+cnt] + add) % self.MOD
res = 0
for carry in range(m + 1):
carry_bits = bin(carry).count('1')
if carry_bits <= k:
res = (res + dp[n][k - carry_bits][carry][m]) % self.MOD
return res
Rust
use std::cmp;
const MOD: i64 = 1_000_000_007;
pub struct Solution;
impl Solution {
pub fn magical_sum(m: i32, k: i32, nums: Vec<i32>) -> i32 {
let n = nums.len();
let m_us = m as usize;
// comb table
let mut c = vec![vec![0i64; m_us + 1]; m_us + 1];
for i in 0..=m_us {
c[i][0] = 1;
c[i][i] = 1;
for j in 1..i {
c[i][j] = (c[i-1][j-1] + c[i-1][j]) % MOD;
}
}
// pow table
let mut pw = vec![vec![1i64; m_us + 1]; n];
for i in 0..n {
for t in 1..=m_us {
pw[i][t] = pw[i][t-1] * nums[i] as i64 % MOD;
}
}
// dp[pos][bits][carry][chosen]
let mut dp = vec![vec![vec![vec![0i64; m_us + 1]; m_us + 1]; (k as usize) + 1]; n + 1];
dp[0][0][0][0] = 1;
for pos in 0..n {
for bits in 0..=(k as usize) {
for carry in 0..=m_us {
for chosen in 0..=m_us {
let cur = dp[pos][bits][carry][chosen];
if cur == 0 { continue }
let remaining = m_us - chosen;
for cnt in 0..=remaining {
let total = carry + cnt;
let new_bits = bits + (total & 1);
let new_carry = total >> 1;
if new_bits <= (k as usize) && new_carry <= m_us {
let add = cur * c[remaining][cnt] % MOD * pw[pos][cnt] % MOD;
dp[pos+1][new_bits][new_carry][chosen+cnt] = (dp[pos+1][new_bits][new_carry][chosen+cnt] + add) % MOD;
}
}
}
}
}
}
let mut res = 0i64;
for carry in 0..=m_us {
let carry_bits = (carry as u32).count_ones() as usize;
if carry_bits <= (k as usize) {
res = (res + dp[n][(k as usize) - carry_bits][carry][m_us]) % MOD;
}
}
res as i32
}
}
TypeScript
class Solution {
magicalSum(m: number, k: number, nums: number[]): number {
const MOD = 1_000_000_007;
const n = nums.length;
const C: number[][] = Array.from({length: m+1}, () => new Array(m+1).fill(0));
for (let i = 0; i <= m; ++i) {
C[i][0] = C[i][i] = 1;
for (let j = 1; j < i; ++j) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
const pw: number[][] = Array.from({length: n}, () => new Array(m+1).fill(1));
for (let i = 0; i < n; ++i) for (let t = 1; t <= m; ++t) pw[i][t] = Math.floor(pw[i][t-1] * nums[i] % MOD);
// dp[pos][bits][carry][chosen]
const dp: number[][][][] = new Array(n+1);
for (let pos = 0; pos <= n; ++pos) {
dp[pos] = new Array(k+1);
for (let bits = 0; bits <= k; ++bits) {
dp[pos][bits] = new Array(m+1);
for (let carry = 0; carry <= m; ++carry) dp[pos][bits][carry] = new Array(m+1).fill(0);
}
}
dp[0][0][0][0] = 1;
for (let pos = 0; pos < n; ++pos) {
for (let bits = 0; bits <= k; ++bits) {
for (let carry = 0; carry <= m; ++carry) {
for (let chosen = 0; chosen <= m; ++chosen) {
const cur = dp[pos][bits][carry][chosen];
if (!cur) continue;
const remaining = m - chosen;
for (let cnt = 0; cnt <= remaining; ++cnt) {
const total = carry + cnt;
const newBits = bits + (total & 1);
const newCarry = total >> 1;
if (newBits <= k && newCarry <= m) {
const add = (cur * C[remaining][cnt] % MOD) * pw[pos][cnt] % MOD;
dp[pos+1][newBits][newCarry][chosen+cnt] = (dp[pos+1][newBits][newCarry][chosen+cnt] + add) % MOD;
}
}
}
}
}
}
let res = 0;
for (let carry = 0; carry <= m; ++carry) {
const carryBits = carry.toString(2).split('1').length - 1;
if (carryBits <= k) res = (res + dp[n][k - carryBits][carry][m]) % MOD;
}
return res;
}
}
Complexity
- ⏰ Time complexity:
O(n * k * m^3)– we iterate overpos(n),bits(k),carry(O(m)),chosen(O(m)) and for each state try up toO(m)values ofcnt, yieldingO(n * k * m^3). - 🧺 Space complexity:
O(n * k * m^2)– the 4D DP table has sizeO(n * k * (m+1) * (m+1)).