2023秋训练三 补充题解

题目

B - Playlist

题目描述

一张有 \(N\) 个歌曲的播放列表,第 \(i\) 首歌曲的长度为 \(T_i\) 秒。好渴鹅要在时刻 \(0\) 开始播放歌曲,每次以相等的概率随机播放任何一个歌曲。请问 \((X+0.5)\) 秒时正在播放第一首歌曲的概率,对 \(998244353\) 取模。

输入格式

\(N\) \(X\)

\(T_1\) \(T_2\) \(\dots\) \(T_N\)

输出格式

假如概率为 \(\cfrac{a}{b}\),那么你需要找到一个整数 \(c\) \((c\in 998244353)\),使得 \(az\equiv y\pmod{998244353}\),你需要输出这个 \(c\)

数据范围

  • \(2\le N\le 10^3\)
  • \(0\le X\le 10^4\)
  • \(1\le T_i\le 10^4\)

E - Product Development

渴鹅公司计划开发有 \(K\) 个参数的产品,最开始参数都是 \(0\),目标是所有的参数都要大于等于 \(P\)。现在有 \(N\) 个开发计划,第 \(i\) 个开发计划需要 \(C_i\) 的成本,会让第 \(j\) \((1\le j\le K)\) 个参数的值加上 \(A_{(i,j)}\),你需要确定渴鹅公司是否能够达到目标,并输出最小成本。不可以输出 \(-1\)

输入格式

\(N\) \(K\) \(P\)

\(C_1\) \(A_{(1,1)}\) \(A_{(1,2)}\) \(\dots\) \(A_{(1,K)}\)

\(C_1\) \(A_{(2,1)}\) \(A_{(2,2)}\) \(\dots\) \(A_{(2,K)}\)

\(\dots\)

\(C_1\) \(A_{(N,1)}\) \(A_{(N,2)}\) \(\dots\) \(A_{(N,K)}\)

数据范围

  • \(1\le N\le 100\)
  • \(1\le K,P\le 5\)
  • \(0\le A_{(i,j)}\le P\) \((1\le i\le N,1\le j\le K)\)
  • \(1\le C_i\le 10^9\) \((1\le i\le N)\)

解法

B

首先是输出格式,其实就是求逆元,我们可以通过快速幂的方式求出逆元。假设 \(f(a,b)=a^b\),那么我们设 \(inv\gets f(n,998244353)\),那么每次就乘上 \(inv\) 进行转移就行了。

然后是 dp。我们设 \(dp_i\) 表示为当前歌曲以时刻 \(i\) 结尾的概率,那么对于任意一个 \(T_j\) \((1\le j\le n)\),如果 \(i-T_j\ge 0\) 的话,我们就可以把 \(dp_{(j-T_j)}\) 乘上 \(inv\)(也就是乘上概率)累加到 \(dp_i\)。因此可以推出式子:

\[dp_i=\sum\limits_{j=1}^{n}dp_{(i-T_j)}\times inv~(1\le i\le X,1\le j\le N,i\ge T_j) \]

最后我们就需要累加答案。因为这里是以时刻 \(i\) 结尾作为状态,而题目中是正在播放的概率,因此我们需要求出 \(\sum\limits_{i=\max(0,x-T_1+1)}^{x}dp_i\),并在输出时摸上 \(998244353\)注意:这题需要开 long long,并且需要边运算、边取模,不然会炸。

#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

const ll kMaxN = 1e4 + 5, kMod = 998244353;

ll t[kMaxN], dp[kMaxN * 2] = {1}, n, x, inv, ans;

ll ksm(ll a, ll b) {
  ll ret = 1;
  for (; b; b >>= 1) {
    (b & 1) && ((ret *= a) %= kMod);
    (a *= a) %= kMod;
  }
  return ret;
}

int main() {
  cin >> n >> x;
  inv = ksm(n, kMod - 2);
  for (ll i = 1; i <= n; i++) {
    cin >> t[i];
  }
  for (ll i = 0; i <= x; i++) {
    for (ll j = 1; j <= n; j++) {
      if (i >= t[j]) {
        (dp[i] += dp[i - t[j]] * inv) %= kMod;
      }
    }
  }
  for (ll i = max(0LL, x - t[1] + 1); i <= x; i++) {
    (ans += dp[i]) %= kMod;
  }
  cout << (ans * inv) % kMod << '\n';
  return 0;
}

E

首看到这道题,你或许就会想到 01 背包 dp。由于 \(1\le K,P\le 5\),那我们几乎可以说是乱搞都能过。首先我们先来考虑 \(K=1\) 的简单情况。由于状态就只剩下第一个也是唯一一个参数的值了,那么我们就设 \(dp_i\) 为达到当前第一个参数的和所能获得的最小代价。\(K\) 等于其他的情况也是同理。那么我们就可以获得如下的状态转移方程矩阵:

\[\begin{array} dp_j=\min(dp_j,dp_{(j-A_{(i,1)})}+c_i) \\ dp_{(j,k)}=\min(dp_{(j,k)},dp_{(j-A_{(i,1)},k-A_{(i,2)})}+c_i) \\ dp_{(j,k,l)}=\min(dp_{(j,k,l)},dp_{(j-A_{(i,1)},k-A_{(i,2)},l-A_{(i,3)})}+c_i) \\ dp_{(j,k,l,p)}=\min(dp_{(j,k,l,p)},dp_{(j-A_{(i,1)},k-A_{(i,2)},l-A_{(i,3)},p-A_{(i,4)})}+c_i) \\ dp_{(j,k,l,p,q)}=\min(dp_{(j,k,l,p,q)},dp_{(j-A_{(i,1)},k-A_{(i,2)},l-A_{(i,3)},p-A_{(i,4)},q-A_{(i,5)})}+c_i) \\ \end{array} \]

但是,这样子也太麻烦了吧?其实我们可以把状态开成一个长度为 \(K\) 的向量,那么我们的操作就简单了许多。我们设计一个函数 \(f(v)\) 用来更改状态,那么在这个函数里面,对于任意的 \(j\) \((1\le j\le K)\)\(v_j\gets \min(P,v_j+A_{(i,j)})\),也就是在对应位置上更改。

那么你也许可能会问:为什么要跟 \(P\)\(\min\) 呢?其实这是一种偷懒行为,因为当一个参数已经大于等于 \(P\) 了,那么就没有意义了;并且所有数都大于等于 \(P\) 的数列有很多,而加上了这个操作我们最后的状态就一定是一个全部都是 \(P\) 的向量。那么我们的操作就简单了许多。

注:C++ 的向量为 vector,映射为 map。而我们可能会发现如果直接压数组那么同层状态就会就会重复遍历,所以我们使用滚动数大法——用一个 \(f\) 来记录上一层状态。(注意最开始的初始状态 \(dp\)\(f\) 数组都需要加进去,好渴鹅就是因为这里 WA 了两发。)

#include <iostream>
#include <vector>
#include <map>

using namespace std;
using ll = long long;

ll n, k, p;

int main() {
  cin >> n >> k >> p;
  vector<ll> c(n + 1);
  vector<vector<ll>> a(n + 1, vector<ll>(k + 1));
  for (ll i = 1; i <= n; i++) {
    cin >> c[i];
    for (ll j = 1; j <= k; j++) {
      cin >> a[i][j];
    }
  }
  map<vector<ll>, ll> dp, f;
  f.insert({vector<ll>(k, 0), 0});
  dp.insert({vector<ll>(k, 0), 0});
  for (ll i = 1; i <= n; i++) {
    for (auto j : f) {
      vector<ll> v = j.first;
      for (ll l = 1; l <= k; l++) {
        v[l - 1] = min(p, v[l - 1] + a[i][l]);
      }
      if (dp.count(v)) {
        dp[v] = min(dp[v], j.second + c[i]);
      } else {
        dp.insert({v, j.second + c[i]});
      }
    }
    f = dp;
  }
  if (dp.count(vector<ll>(k, p))) {
    cout << dp[vector<ll>(k, p)] << '\n';
  } else {
    cout << "-1\n";
  }
  return 0;
}
posted @ 2023-12-01 17:34  haokee  阅读(10)  评论(0)    收藏  举报