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\)

  1. 恰好有\(n\)个元素。
  2. 元素的值在\(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;
}
posted @ 2025-07-23 00:53  maburb  阅读(198)  评论(0)    收藏  举报