题目描述
难度分:2099
输入n,k(1≤k≤n≤3000)。
输出有多少个多重集合,满足如下条件:
- 恰好有n个元素。
- 每个元素都是12i,即1,12,14,18,…中的一个。
- 这 n 个数的和恰好等于k。
答案模998244353。
输入样例1
4 2
输出样例1
2
输入样例2
2525 425
输出样例2
687232272
算法
动态规划
代码很短,但是这思路真难啊,我太菜了!
状态定义
dp[i][j]表示i个数和为j的多重集合数量,在这个定义下答案就是dp[n][k]。初始化dp[0][0]=1,空集合算一种方案。
状态转移
分为两种情况考虑:
- 如果这i个数里面有1,去掉这个1就变成一个子问题“i−1个数和为j的多重集合数量”,状态转移方程为dp[i][j]=dp[i−1][j−1]。
- 如果这i个数里没有1,实际上就相当于求“i个数和为2j的多重集合数量”,因为1=12+14+14等价于2=1+12+12,这就又凑了个1出来,并且凑出目标和的数还是12i。状态转移方程为dp[i][j]=dp[i][2j]。
以上两种情况加起来就是dp[i][j],只需要枚举j的取值即可。由于dp[i][j]的转移来源为dp[i][2j],所以要倒序遍历j。j不可能大于i,因为即使所有元素都取最大的1,也只能凑出i这个和,是不可能得到j的,所以j从i取到0。
复杂度分析
时间复杂度
状态数量是O(n2),单次转移是O(1)的,所以整个算法的时间复杂度为O(n2)。
空间复杂度
空间消耗的瓶颈就是DP
矩阵,所以额外空间复杂度为O(n2)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353, N = 3010;
int n, k, dp[N][N];
int main() {
scanf("%d%d", &n, &k);
dp[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = i; j >= 0; j--) {
if(j >= 1) dp[i][j] = dp[i - 1][j - 1];
if(j*2 <= i) {
dp[i][j] = (dp[i][j] + dp[i][j<<1]) % MOD;
}
}
}
printf("%d\n", dp[n][k]);
return 0;
}