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;
}

浙公网安备 33010602011771号