Educational Codeforces Round 181 (Rated for Div. 2)
A. Difficult Contest
题意:出现排列一个字符串,使得没有\(FFT, NTT\)两个字符串。
把所有\(T\)移到最前面一定符合条件。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
for (int i = 0, j = 0; i < n;) {
while (i < n && s[i] == 'T') {
++ i;
}
j = std::max(j, i + 1);
while (j < n && s[j] != 'T') {
++ j;
}
if (i >= n || j >= n) {
break;
}
std::swap(s[i], s[j]);
}
std::cout << s << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Left and Down
题意:两个值\(x, y\),你要把它们都减为\(0\),选择一个二元组集合\(\{(x_1, y_1), (x_2, y_2)...\}\),使得所有\(x_i, y_i \leq k\),每次从集合里选择一个是\(x = x - x_i, y = y - y_i\)。求集合最小的大小。
如果想要只用一个二元组满足条件,那么每次\(x, y\)都各减一个数,减相同次数变成\(0\)。记减了\(d\)次,那么\(d\)至少是\(gcd(x, y)\)。这样\(x\)每次减\(\frac{x}{d}\),\(y\)每次减\(\frac{y}{d}\),\(d\)次后就都变成了\(0\)。因为有\(k\)这个限制,我们希望\(\frac{x}{d}, \frac{y}{d}\)都小于等于\(k\),那么\(d\)越大越好,则\(gcd(x, y)\)已经是最大的\(d\)了。那么如果\(\max(\frac{x}{d}, \frac{y}{d}) \leq k\),就只需要一次。
否则我们搞\((x, 0)\)和\((0, y)\)这两个就行。答案是\(2\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 a, b, k;
std::cin >> a >> b >> k;
i64 d = std::gcd(a, b);
if (a / d <= k && b / d <= k) {
std::cout << 1 << "\n";
} else {
std::cout << 2 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. Count Good Numbers
题意:一个数是好的,那么这个数所有的质因数都大于\(10\)。求\([l, r]\)里有多少好的数。
一个经典\(trick\),求\([l, r]\)可以转换为\([1, r]\)的个数减\([1, l - 1]\)的个数。
那么考虑\([1, n]\)有多少好的数,发现小于\(10\)的质数只有\(\{2, 3, 5, 7\}\),考虑容斥。也就是分别减去\(2, 3, 5, 7\)的倍数,然后加上\(2\times 3, 2\times 5...\)这种两个相乘的倍数,再减去三个相乘的,最后加上四个相乘的。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
std::vector<int> primes{2, 3, 5, 7};
void solve() {
i64 l, r;
std::cin >> l >> r;
auto get = [&](i64 n) -> i64 {
i64 res = 0;
for (int i = 0; i < 16; ++ i) {
i64 mul = 1;
i64 t = 1;
for (int j = 0; j < 4; ++ j) {
if (i >> j & 1) {
mul *= primes[j];
t *= -1;
}
}
res += n / mul * t;
}
return res;
};
std::cout << get(r) - get(l - 1) << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. Segments Covering
题意:有\(n\)个区间,每个区间有概率出现,求\([1, m]\)每个位置恰好被一个区间覆盖的概率。
记\(p_i\)为区间\(i\)出现的概率,\(q_i\)为不出现的概率。对于一个合法的方案,记\(a_1, a_2, .. a_x\)是出现过的区间,\(b_1, b_2, .. b_y\)是没出现的区间,那么这个方案的概率是\(p_{a_1}\times p_{a_2}\times...\times p_{a_x} \times q_{b_1}\times ... \times q_{b_y}\)。
考虑\(dp\),可以记\(Q\)为所有\(q_i\)的乘积,记\(f_i\)为覆盖\([1, i]\)的概率,那么对于每个\(r_j == i\)的\((l_j, r_j, p_j, q_j)\),有\(f_i \sum_{r_j = i} f[l_j - 1] \times \frac{p_j}{q_j}\)。
最后我们得到的答案是一群\(p\)除上一群\(q\),那么我们直接乘上\(Q\),这些被除掉的\(q\)就没了,剩下的就是选择的区间的\(p\)和没选择的区间的\(q\)相乘,正好是我们要求的。
注意题解对\(p_i, q_i\)重新定义了,不是题目里的意思
代码省略取模类。
点击查看代码
constexpr int P = 998244353;
using Z = MInt<P>;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::pair<int, Z>>> a(m + 1);
Z x = 1;
for (int i = 0; i < n; ++ i) {
int l, r, p, q;
std::cin >> l >> r >> p >> q;
Z t = (Z)p / q;
a[r].emplace_back(l, t / (1 - t));
x *= (1 - t);
}
std::vector<Z> f(m + 1, 0);
f[0] = 1;
for (int i = 1; i <= m; ++ i) {
for (auto & [l, p] : a[i]) {
f[i] += f[l - 1] * p;
}
}
std::cout << f[m] * x << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E. Sets of Complementary Sums
题意:一个数组\(a\)生成一个集合\(Q\),方式是取\(s\)为\(a\)的元素和,\(Q_i = s - a_i\)。\(Q\)中每个元素只出现一次。求能生成多少满足条件的\(Q\):
- 恰好有\(n\)个元素。
- 元素的值在\(1\)到\(x\)之间。
这题主要是观察性质,发现每一个集合\(Q\),对于生成它的数组\(a\),添加\(a\)中一个已经出现的数\(k\),那么可以得到\(Q'\)满足\(Q'_i = Q_i + k\)。那么记一个\(a1_i = a_i - mina + 1\),其中\(mina\)是\(a\)中最小的元素,就得到了一个一定包含\(1\)的集合,这个数组不断添加\(1\),添加\(mina-1\)次就能生成到\(Q\)。那么我们只需要一个有\(n\)个不同数,且包含\(1\)的数组,就可以用给它推出若干集合。
那么考虑\(dp\),我们可以从小到大选\(n\)个不同的数,记\(f[i][j]\)为选了\(i\)个数,已经确定总数至少为\(j\)的方案数,这个总和是算上后面位置的总和的,我们转移的时候,是枚举第\(i\)个数比第\(i-1\)个数大多少,那么因为后面每个数都大于第\(i\)个数,所以后面每个数都比第\(i-1\)个数大这么多。如果第\(i\)个数比第\(i-1\)个数大\(k\),那么有\(f[i][j] → f[i + 1][j + (n - i + 1) \times k]\)。
但这个似乎很爆炸,那么我们满足条件的数组的最小总和是多少?显然\(1\)到\(n\)这\(n\)个数总和是最小的,那么总和是\(\frac{n(n+1)}{2}\),最小值是\(1\),所以\(Q\)的最大值就有\(\frac{n(n+1)}{2}-1\),这个值不能超过\(x\),那么\(n\)大概是\(\sqrt{x}\)这个级别,于是我们的状态可以优化到一个\(x\sqrt{x}\)。
不过还是不能接受转移时的复杂度,我们发现对于一个位置\(i\),总和为\(j\),如果前面\(i-1\)可以选\([1, k]\)这\(k\)个数,那么分别是从\(f[i-1][j-(n-i+1)\times 1], f[i-1][j-(n-i+1)\times 2], ..., f[i-1][(j-(n-i+1)\times k]\)转移过来,这个和背包的转移十分相似,可以发现\(f[i][j] = f[i][j - i] + f[i-1][j - i]\)。于是我们就可以\(O(1)\)转移。然后答案就是\(\sum_{i=1}^{x+1} f[n][i] \times (x+1-i+1)\)。因为固定有\(1\),所以总和最多\(x+1\),然后对于少于\(x+1\)的部分,每添加一个\(1\)就能生成一个不同的集合,于是需要乘上\((x+1-i+1)\)。
代码实现有点出入,我是先把\(1\)取出来,那么总和最多\(x-n+1\),然后只需要\(dp\ n-1\)轮。代码省略取模类。
点击查看代码
constexpr int P = 998244353;
using Z = MInt<P>;
void solve() {
int n, x;
std::cin >> n >> x;
if ((i64)n * (n + 1) / 2 - 1 > x) {
std::cout << 0 << "\n";
return;
}
if (n == 1) {
std::cout << x << "\n";
return;
}
x = x - n + 1;
std::vector<Z> f(x + 1);
f[0] = 1;
for (int i = 1; i < n; ++ i) {
std::vector<Z> g(x + 1);
for (int j = i; j <= x; ++ j) {
g[j] += f[j - i] + g[j - i];
}
f = g;
}
Z ans = 0;
for (int i = 1; i <= x; ++ i) {
ans += f[i] * (x - i + 1);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}