2023秋训练三

Merge Slimes

首先,我们看到,两个大小为 \(S\) 的滑雪板可以合成为 \(2\times S\) 的滑雪板。通过这个特性,我们就已经知道了,对于任意的 \(S\),只会合成比 \(S\) 大的滑雪板。虽然说了跟没说一样,但是我们就知道可以从小到大枚举 \(S\),然后合成就行了。

由于 \(S\) 的范围十分大,因此我们使用一个 map 来进行存储。下标就是滑雪板的大小,值就是滑雪板的数量。这是,你可能会问了:在迭代时插入元素会不会 RE 啊?其实 map 是关联式容器,因此不会影响迭代。

时间复杂度:\(\mathcal O(n\log_2 n)=\mathcal O(\)能过\()\)

#include <iostream>
#include <map>

using namespace std;
using ll = long long;

ll n, ans;
map<ll, ll> f;

int main() {
  cin >> n;
  for (ll i = 1, s, c; i <= n; i++) {
    cin >> s >> c;
    f[s] += c;
  }
  for (auto [i, j] : f) {
    f[i] -= (j - j % 2);
    f[i * 2] += (j - j % 2) / 2;
  }
  for (auto [i, j] : f) {
    ans += j;
  }
  cout << ans << '\n';
  return 0;
}

Mex and Update

这一题我们可以使用一个 set 来维护出现过的数,每次查询是 \(\mathcal O(\log_2 n)\) 的。我们再使用一个 map 记录每一个数出现过的数量,再进行更改。如果对应位置上的数量变成了 \(0\),就说明这个数已经不存在了,因此我们在 set 中删除掉这个数;如果更改之后的数正好等于 \(1\),这就说明我们该把它加入进 set 里面了。

但是遍历集合内所有的元素并求出答案,最坏情况下是 \(\mathcal O(n\log_2 n)\) 的,但是这题 \(1\le Q\le 2\times 10^5\),这样模拟会超时。

我们试着转变思路,我们把没有出现过的数放进集合里面。那你肯定会问:没出现过的数那么多,你把它们全部都放进去这不直接吃席啊?但是我们知道 \(n\) 以内,一定有一个数是没有出现过的。因此我们就只存储没有出现过并且小于等于 \(n\) 的数,这样子我们就可以实现 \(\mathcal O(\log_2 n)\) 的插入、删除、查询。

时间复杂度为 \(\mathcal O(q\log_2 n)=\mathcal O(\)能过\()\)

#include <iostream>
#include <set>

using namespace std;
using ll = long long;

const ll kMaxN = 2e5 + 5;

ll f[kMaxN], a[kMaxN], n, q;
set<ll> s;

int main() {
  cin >> n >> q;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
    a[i] <= n && f[a[i]]++;
  }
  for (ll i = 0; i <= n; i++) {
    if (!f[i]) {
      s.insert(i);
    }
  }
  for (ll i, x; q; q--) {
    cin >> i >> x;
    a[i] <= n && f[a[i]]--;
    if (a[i] <= n && !f[a[i]]) {
      s.insert(a[i]);
    }
    (a[i] = x) <= n && f[a[i]]++;
    if (a[i] <= n && s.count(a[i])) {
      s.erase(a[i]);
    }
    cout << *s.begin() << '\n';
  }
  return 0;
}

#(subset sum = K) with Add and Erase

首先,这是一个非常简单的计数类 01 背包问题。我们设 \(dp_{(i,j)}\) 为当前已经加入了 \(i\) 个元素进入背包,选了和为 \(j\) 的元素的方案数,那么 \(dp_{(i,j)}=dp_{(i,j)}+dp_{(i-1,j-x)}\)。由于这一层状态会影响到上一层状态,因此我们需要一个二维数组,但是很显然空间并不允许我们这么做。最后,我们发现了只要将 \(j\) 的遍历改为倒序就可以解决这个问题,因为每次都是向后转移。

接下来在考虑删除。我们知道进入背包的顺序是可以随意打乱的,然后由于 \(dp_i=dp_i+dp_{(i-x)}\),所以我们可以使用加的逆运算——大名鼎鼎的减法,因此我们就将 \(dp_i\) 减去 \(dp_{(i-x)}\)。这里的循环需要改为倒叙,因为这是背包的反向操作。

最后,便是对 \(998244353\) 进行取模,注意在每一步运算之后都会取模。理论上这里的减法是不会减到负数的,但是由于取了模的原因,我们需要加上模数最后在取模。

时间复杂度为 \(\mathcal O(qk)=\mathcal O(\)能过\()\)

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 5005, kMod = 998244353;

ll dp[kMaxN], q, k, x;
char o;

int main() {
  dp[0] = 1;
  for (cin >> q >> k; q; q--) {
    cin >> o >> x;
    if (o == '+') {
      for (ll i = k; i >= x; i--) {
        (dp[i] += dp[i - x]) %= kMod;
      }
    } else {
      for (ll i = x; i <= k; i++) {
        ((dp[i] -= dp[i - x]) += kMod) %= kMod;
      }
    }
    cout << dp[k] << '\n';
  }
  return 0;
}
posted @ 2023-11-27 17:32  haokee  阅读(11)  评论(0)    收藏  举报