考点
思路
可以发现,对于一个和等于K的序列,它对结果产生的贡献为2 ^ (n - 序列长度)
比如对于{1, 2, 2, 3}, K = 4,显然{2, 2}满足条件,那么由它延伸可以得到{2, 2}、{1, 2, 2}、{2, 2, 3}、{1, 2, 2, 3}四种序列,那么产生的贡献就是2 ^ (4 - 2) = 4
由此很容易设计状态转移方程:
1 2 3 4 f[i][j][k]: 考虑前i个数,序列和为j、且序列长度为k的个数 使用Push方向, 即已知f[i],考虑对f[i+1]的转移 不选nums[i+1], f[i+1][j][k] += f[i][j][k] 选nums[i+1], f[i+1][j+nums[i+1]][k+1] += f[i][j][k]
得到第一版AC代码:
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 class Solution { public : static const int maxn = 1e2 + 50 , mod = 1e9 + 7 ; int n; long long f[maxn][maxn][maxn]; long long ksm (long long x, int y) { long long res = 1 ; while (y) { if (y & 1 ) res = res * x % mod; x = x * x % mod; y >>= 1 ; } return res; } int sumOfPower (vector<int >& nums, int K) { n = nums.size (); memset (f, 0 , sizeof (f)); f[0 ][0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++i) { int x = nums[i]; for (int j = 0 ; j <= K; ++j) { for (int k = 0 ; k <= i; ++k) { f[i + 1 ][j][k] = (f[i + 1 ][j][k]+f[i][j][k])%mod; if (j + x <= K) f[i + 1 ][j + x][k + 1 ] = (f[i + 1 ][j + x][k + 1 ]+f[i][j][k])%mod; } } } int ans = 0 ; for (int i = 1 ; i <= n; ++i) ans = (ans + (1LL * f[n][K][i] * ksm (2 , n - i) % mod)) % 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 class Solution { public : static const int maxn = 1e2 + 50 , mod = 1e9 + 7 ; int n; long long f[maxn][maxn]; long long ksm (long long x, int y) { long long res = 1 ; while (y) { if (y & 1 ) res = res * x % mod; x = x * x % mod; y >>= 1 ; } return res; } int sumOfPower (vector<int >& nums, int K) { n = nums.size (); memset (f, 0 , sizeof (f)); f[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++i) { int x = nums[i]; for (int j = K; j >= 0 ; --j) { for (int k = 0 ; k <= i; ++k) { if (j + x <= K) f[j + x][k + 1 ] = (f[j + x][k + 1 ] + f[j][k]) % mod; } } } int ans = 0 ; for (int i = 1 ; i <= n; ++i) ans = (ans + (1LL * f[K][i] * ksm (2 , n - i) % mod)) % mod; return ans; } };
这样还是慢了,通过观察可以发现,压根不需要记录序列长度那一维:
1 2 3 4 5 6 7 8 9 10 f[i][j]: 前i个数, 考虑所有和为j的序列,记录它们的2 ^ (i - 序列长度)之和 假设现在的f[i][j]记录的是两个序列: 1 3 1 1 2 同样是Push方向,考虑对f[i+1]的转移 若nums[i+1]并不加入到当前序列,只是用以重排列,那么f[i+1][j] += f[i][j] * 2 这是显然的,因为{1, 3}可以变成{1, 3, 不选nums[i+1]}和{1, 3, nums[i+1]}两种状态 若nums[i+1]加入当前序列, f[i+1][j+nums[i+1]] += f[i][j] 这是显然的,之前是{1, 3}, 一共n个数, 贡献就是2 ^ (n - 2) 而现在是{1, 3, nums[i+1]}, 一共n+1个数, 贡献就是2 ^ (n + 1 - (2 + 1)) = 2 ^ (n - 2), 并没有变
得到第二版代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public : static const int maxn = 1e2 + 50 , mod = 1e9 + 7 ; int n; long long f[maxn][maxn]; int sumOfPower (vector<int >& nums, int K) { n = nums.size (); memset (f, 0 , sizeof (f)); f[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++i) { for (int j = K; j >= 0 ; --j) { f[i + 1 ][j] = (f[i + 1 ][j] + f[i][j] * 2 % mod) % mod; if (j + nums[i] <= K) f[i + 1 ][j + nums[i]] = (f[i + 1 ][j + nums[i]] + f[i][j]) % mod; } } return f[n][K]; } };
当然,这里同样可以使用滚动数组,时间和空间效率拉到极致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public : static const int maxn = 1e2 + 50 , mod = 1e9 + 7 ; int n; long long f[maxn]; int sumOfPower (vector<int >& nums, int K) { n = nums.size (), memset (f, 0 , sizeof (f)); f[0 ] = 1 ; for (int i = 0 ; i < n; ++i) { for (int j = K; j >= 0 ; --j) { if (j + nums[i] <= K) f[j + nums[i]] = (f[j + nums[i]] + f[j]) % mod; f[j] = f[j] * 2 % mod; } } return f[K]; } };