#P2821. 第3题-小美的数组
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 80
            Accepted: 20
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2025年4月12日-算法岗
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第3题-小美的数组
题解
题目描述
给定一个由 n 个正整数构成的数组 {a1,a2,…,an},要求从中选择至多 k 个数, 使得这些数的乘积恰好为 p 的倍数。
输入时,第一行给出正整数 n,k,p(其中 1≤n,p≤100 且 1≤k≤n),代表数组的元素数量、最多可以挑选的元素个数以及倍数 p;第二行给出 n 个正整数 a1,a2,…,an(每个 ai 满足 1≤ai≤109)。
输出时,将满足条件的选数方案总数对 109+7 取模后输出。
题目思路
题目要求选取的数的乘积必须为 p 的倍数,即其乘积可以被 p 整除。注意到:
- 如果 p=1,那么任何数的乘积均为 1 的倍数,此时方案数即所有选取个数不超过 k 的子集数(包括空集)。
 - 对于 p>1,我们要求乘积 ∏选中的数字≡0(modp)。
 
由于 n、k 和 p 都很小(最多 100),可以采用动态规划(DP)方法解决。
DP 状态定义
设 dp[i][j][r] 表示前 i 个数中选取 j 个数,且这些数的乘积对 p 取余结果为 r 的方案数。
- 初始状态:dp[0][0][1modp]=1(空集的乘积定义为 1,注意空集只有在 p=1 时满足条件,但在转移中仍需初始化)。
 - 状态转移:
- 不选第 i 个数:则 dp[i][j][r] 的方案数会传递到 dp[i+1][j][r]。
 - 选第 i 个数:设当前 ai+1 取值为 x,那么新的余数为 (r∗x)modp,同时选数个数增加 1。
 
 
最后答案为
ans=j=0∑kdp[n][j][0]即选择任意个数(最多 k 个)后乘积余 p 等于 0 的方案数。
C++
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 1000000007;
int main(){
    int n, k, p;
    cin >> n >> k >> p;
    vector<int> a(n);
    for (int i=0; i<n; i++){
        cin >> a[i];
    }
    // 初始化三维DP数组,dp[i][j][r]表示前i个数字中选择j个,乘积模p余数为r的方案数
    vector<vector<vector<int>>> dp(n+1, vector<vector<int>>(k+1, vector<int>(p, 0)));
    // 空集的乘积默认为1,因此余数为 (1 % p)
    dp[0][0][1 % p] = 1;
    for (int i=0; i<n; i++){
        for (int j=0; j<=k; j++){
            for (int r=0; r<p; r++){
                if(dp[i][j][r] != 0){
                    // 不选第i个数字,状态不改变
                    dp[i+1][j][r] = (dp[i+1][j][r] + dp[i][j][r]) % MOD;
                    // 如果还能选数字,则选择第i个数字
                    if(j + 1 <= k){
                        int newR = (int)((1LL * r * (a[i] % p)) % p);
                        dp[i+1][j+1][newR] = (dp[i+1][j+1][newR] + dp[i][j][r]) % MOD;
                    }
                }
            }
        }
    }
    // 答案为所有选择个数不超过k的方案中乘积余数为0的方案和
    int ans = 0;
    for (int j=0; j<=k; j++){
        ans = (ans + dp[n][j][0]) % MOD;
    }
    cout << ans << endl;
    return 0;
}
Python
MOD = 10**9 + 7
def main():
    import sys
    input_data = sys.stdin.read().split()
    # 读取输入,n, k, p分别代表数字个数、最多选择个数、倍数p
    n = int(input_data[0])
    k = int(input_data[1])
    p = int(input_data[2])
    a = list(map(int, input_data[3:3+n]))
    
    # 初始化dp数组,dp[i][j][r]表示前i个数字中选取j个,乘积模p余数为r的方案数
    dp = [[[0] * p for _ in range(k+1)] for __ in range(n+1)]
    dp[0][0][1 % p] = 1  # 空集乘积定义为1
    
    for i in range(n):
        for j in range(k+1):
            for r in range(p):
                if dp[i][j][r]:
                    # 不选当前数字
                    dp[i+1][j][r] = (dp[i+1][j][r] + dp[i][j][r]) % MOD
                    # 选当前数字,如果还未达到选择数量上限
                    if j + 1 <= k:
                        new_r = (r * (a[i] % p)) % p
                        dp[i+1][j+1][new_r] = (dp[i+1][j+1][new_r] + dp[i][j][r]) % MOD
    
    # 累加所有选择个数不超过k的方案中,余数为0的个数
    ans = 0
    for j in range(k+1):
        ans = (ans + dp[n][j][0]) % MOD
    print(ans)
if __name__ == '__main__':
    main()
Java
import java.util.*;
import java.io.*;
public class Main {
    static final int MOD = 1000000007;
    public static void main(String[] args) throws IOException {
        // 利用BufferedReader读取输入
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] params = br.readLine().split("\\s+");
        int n = Integer.parseInt(params[0]);
        int k = Integer.parseInt(params[1]);
        int p = Integer.parseInt(params[2]);
        
        String[] numStr = br.readLine().split("\\s+");
        int[] a = new int[n];
        for (int i=0; i<n; i++){
            a[i] = Integer.parseInt(numStr[i]);
        }
        
        // dp[i][j][r]表示前i个数字中选取j个数字,乘积模p余数为r的方案数
        int[][][] dp = new int[n+1][k+1][p];
        dp[0][0][1 % p] = 1;  // 空集的乘积默认为1
        
        // 状态转移
        for (int i = 0; i < n; i++){
            for (int j = 0; j <= k; j++){
                for (int r = 0; r < p; r++){
                    if(dp[i][j][r] != 0){
                        // 不选第i个数字
                        dp[i+1][j][r] = (dp[i+1][j][r] + dp[i][j][r]) % MOD;
                        // 如果还可以选数字,则选第i个数字
                        if(j + 1 <= k){
                            int newR = (int)(((long)r * (a[i] % p)) % p);
                            dp[i+1][j+1][newR] = (dp[i+1][j+1][newR] + dp[i][j][r]) % MOD;
                        }
                    }
                }
            }
        }
        
        // 累加所有选择个数不超过k的方案中乘积余数为0的方案数
        int ans = 0;
        for (int j = 0; j <= k; j++){
            ans = (ans + dp[n][j][0]) % MOD;
        }
        System.out.println(ans);
    }
}
        题目内容
对于给定的由n个正整数构成的数组 {a1,a2,…,an} ,从中选择至多 k 个数,使得这些数的乘积恰好为 p 的倍数。
小美想知道,一共有多少种满足条件的选数方案?由于答案可能很大,请将答案对 (109+7) 取模后输
输入描述
第一行输入三个正整数 n,k,p(1≦n,p≦100;1≦k≦n) 代表数组中的元素数量、至多挑选的元素数量,位期的涨取用
第二方输入 n 个正整数 a1,a2,...,an(1≤ai≤109) 代表教组中的元素。
输出描述
在一行上输出一个整数,代表满足条件的选数方案。由于答案可能很大,请将答案对 (109+7) 取模后输出。
样例1
输入
5 5 30
1 2 3 4 5
输出
6