背包dp 组合数学 二项式定理

解法

首先观察到极大的 ,这似乎让我们无从下手,但是不难发现 极小 ,这不禁让我们想到 是一个关键的突破口。

考虑 dp。令 表示为以第 个元素为结尾的所有区间的的和的 次方之和,即

每次加入一个元素 时,假设之前的和为 ,我们可以利用二项式定理

展开 ,即

可以发现这个式子里面的 都是可以通过预处理提前计算得到,而 就是 。通过这种方式,我们可以递推得到 dp 数组。最后 就是答案了。注意取模。

复杂度分析

时间复杂度

读入 ,预处理组合数 ,预处理 ,dp 过程 ,总时间复杂度 。本题下 ,因此运算量级大概是 级别的,可以通过。如果不预处理 的话,时间复杂度

空间复杂度

数组存储 ,组合数数组 数组 ,dp 数组 ,总空间复杂度 。如果不处理 数组并对 dp 数组进行滚动数组优化的话空间复杂度为

代码

采用滚动数组优化 dp。

#include <iostream>
#include <vector>
 
using namespace std;
using LL = long long;
 
const LL kMod = 998244353;
 
LL n, K, ans;
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
 
  // 读入
  cin >> n >> K;
  vector<LL> a(n + 1);
  for (LL i = 1; i <= n; i++) {
    cin >> a[i];
  }
 
  // 预处理组合数
  vector<vector<LL>> C(K + 1, vector<LL>(K + 1));
  for (LL k = 0; k <= K; k++) {
    C[k][0] = 1;
    for (LL m = 1; m <= k; m++) {
      C[k][m] = (C[k - 1][m - 1] + C[k - 1][m]) % kMod;
    }
  }
 
  // 预处理 a[i]^k
  vector<vector<LL>> powA(n + 1, vector<LL>(K + 1));
  for (LL i = 1; i <= n; i++) {
    powA[i][0] = 1;
    LL x = a[i] % kMod;
    for (LL k = 1; k <= K; k++) {
      powA[i][k] = powA[i][k - 1] * x % kMod;
    }
  }
 
  // dp 过程及统计答案
  vector<LL> dp(K + 1);
  for (LL i = 1; i <= n; i++) {
    vector<LL> f(K + 1);
    for (LL k = 0; k <= K; k++) {
      for (LL m = 0; m <= k; m++) {
        LL trm = C[k][m] * powA[i][k - m] % kMod, prv = dp[m];
        if (m == 0) {  // 注意对零次方的特殊判断
          prv = (prv + 1) % kMod;
        }
        f[k] = (f[k] + trm * prv) % kMod;
      }
    }
    dp = f;
    ans = (ans + dp[K]) % kMod;
  }
  cout << ans << "\n";
  return 0;
}