Codeforces Round 1013 (Div. 3)


A. Olympiad Date

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(10);
    a[0] = 3;
    a[1] = 1;
    a[3] = 1;
    a[2] = 2;
    a[5] = 1;
    std::vector<int> b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    std::vector<int> cnt(10);
    for (int i = 0; i < n; ++ i) {
    	cnt[b[i]] = std::min(cnt[b[i]] + 1, a[b[i]]);
    	if (cnt == a) {
    		std::cout << i + 1 << "\n";
    		return;
    	}
    }

    std::cout << 0 << "\n";
}

B. Team Training

题意:把数组划分为若干个集合,每个集合的价值为集合大小乘集合最小值。求最多划分出来几个价值大于等于\(x\)的集合。

从大到小枚举,因为优先用大的数可以使当前集合大小最小,也就是用最少的数。

点击查看代码
void solve() {
    int n, x;
    std::cin >> n >> x;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::ranges::sort(a);
    int ans = 0;
    for (int i = n - 1, cnt = 0; i >= 0; -- i) {
    	++ cnt;
    	if ((i64)cnt * a[i] >= x) {
    		++ ans;
    		cnt = 0;
    	}
    }

    std::cout << ans << "\n";
}

C. Combination Lock

题意:构造一个长度为\(n\)的排列,使得在接下来\(n-1\)次右移中都有一个位置使得\(p_i = i\)

\(i\)需要右移\(a_i\)次可以到\(i\),那么实际上\(a\)就是\(0\)\(n-1\)的一个排列。发现\(n\)为奇数的情况,从大到小排可以满足这个条件。
而偶数不可以(猜的)

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    if (n % 2 == 0) {
    	std::cout << -1 << "\n";
    	return;
    }

    std::vector<int> ans(n);
    std::ranges::iota(ans, 1);
    std::reverse(ans.begin(), ans.end());
    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n - 1];
    }
}

D. Place of the Olympiad

题意:给你一个\(n\times m\)的矩阵,你要放\(k\)个元素进去,使得每行最大连续子段的最大值最小。

二分。
如果一行最多放\(mid\)个元素,那么这一行总共可以放\(\lfloor \frac{m}{mid + 1} \rfloor + m \% (mid + 1)\)个元素。

点击查看代码
void solve() {
    i64 n, m, k;
    std::cin >> n >> m >> k;

    auto check = [&](i64 len) -> i64 {
    	i64 cnt = m / (len + 1) * len + m % (len + 1);
    	return n * cnt;
    };

    i64 l = 1, r = m;
    while (l < r) {
    	i64 mid = l + r >> 1ll;
    	if (check(mid) >= k) {
    		r = mid;
    	} else {
    		l = mid + 1;
    	}
    }

    std::cout << l << "\n";
}

E. Interesting Ratio

题意:求\([1, n]\)中有多少对数\(a, b\)满足\(a < b, \frac{lcm(a, b)}{gcd(a, b)}\)是质数。

\(lcm(a, b) = \frac{ab}{gcd(a, b)}, \frac{lcm(a, b)}{gcd(a, b)} = \frac{a}{gcd(a, b)} \times \frac{b}{gcd(a, b)}\),显然只有在\(a\)\(b\)中有一个数是\(gcd\)的时候且另一个数除\(gcd\)是质数的情况才合法。
那么我们就是要求每个\(i\)\([i + 1, n]\)内的质数倍数的个数。
预处理筛质数,然后二分求即可。

点击查看代码
std::vector<int> minp, primes;

void sieve(int n) {
    minp.assign(n + 1, 0);
    primes.clear();
    
    for (int i = 2; i <= n; ++ i) {
        if (minp[i] == 0) {
            minp[i] = i;
            primes.push_back(i);
        }
        
        for (auto p : primes) {
            if (i * p > n) {
                break;
            }
            minp[i * p] = p;
            if (p == minp[i]) {
                break;
            }
        }
    }
}

void solve() {
    int n;
    std::cin >> n;
    i64 ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	ans += std::ranges::upper_bound(primes, n / i) - primes.begin();
    }

    std::cout << ans << "\n";
}

F. Igor and Mountain

题意:给你一个\(n\times m\)的矩阵,有些地方可以走有些地方不能走。每一行你最多走两个点,当前行只能到下一行,且每次走的两个的欧拉距离不能超过\(d\)。求从最低层到最高层的走法方案数。

\(f[0/1][i][j]\)表示在第\(i\)行走了\(1\)个或者\(2\)个点现在在第\(j\)列的方案数。
\(sum[0/1][i][j]\)表示\(f[0/1][i]\)的前缀和。
\(L[j], R[j]\)表示跨一行的情况下可以\(j\)可以走到点的左右边界。
那么\(f[0][i][j] = sum[0][i - 1][R[j]] - sum[0][i - 1][L[j] - 1] + sum[1][i - 1][R[j]] - sum[1][i - 1][L[j] - 1]\),意味从上一行到当前点。
然后考虑当前行走两个点的情况,那么\(f[1][i][j] = sum[0][i][j + k] - sum[0][i][j - k - 1] - f[0][i][j]\)
代码省略取模类。

点击查看代码
void solve() {
	int n, m, k;
	std::cin >> n >> m >> k;
	std::vector<std::string> s(n + 1);
	for (int i = 1; i <= n; ++ i) {
		std::cin >> s[i];
	}

	std::reverse(s.begin() + 1, s.end());

	auto get_dist = [&](int x1, int y1, int x2, int y2) -> int {
		int dx = x1 - x2, dy = y1 - y2;
		return dx * dx + dy * dy;
	};

	std::vector f(2, std::vector(n + 1, std::vector<Z>(m + 1)));
	std::vector sum(2, std::vector(n + 1, std::vector<Z>(m + 1)));
	for (int j = 1; j <= m; ++ j) {
		f[0][1][j] = s[1][j - 1] == 'X';
		sum[0][1][j] = sum[0][1][j - 1] + f[0][1][j];
	}

	for (int j = 1; j <= m; ++ j) {
		if (s[1][j - 1] == 'X') {
			int l = std::max(1, j - k), r = std::min(m, j + k);
			f[1][1][j] = sum[0][1][r] - sum[0][1][l - 1] - f[0][1][j];
		}
		sum[1][1][j] = sum[1][1][j - 1] + f[1][1][j];
	}

	std::vector<int> L(m + 1), R(m + 1);
	for (int i = 1, j = 1; i <= m; ++ i) {
		while (get_dist(1, i, 0, j) > k * k) {
			++ j;
		}

		L[i] = j;
	}

	for (int i = m, j = m; i >= 1; -- i) {
		while (get_dist(1, i, 0, j) > k * k) {
			-- j;
		}

		R[i] = j;
	}

	for (int i = 2; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			if (s[i][j - 1] == 'X') {
				f[0][i][j] += sum[0][i - 1][R[j]] - sum[0][i - 1][L[j] - 1];
				f[0][i][j] += sum[1][i - 1][R[j]] - sum[1][i - 1][L[j] - 1];
			}

			sum[0][i][j] = sum[0][i][j - 1] + f[0][i][j];
		}

		for (int j = 1; j <= m; ++ j) {
			if (s[i][j - 1] == 'X') {
				int l = std::max(1, j - k), r = std::min(m, j + k);
				f[1][i][j] = sum[0][i][r] - sum[0][i][l - 1] - f[0][i][j];
			}
			sum[1][i][j] = sum[1][i][j - 1] + f[1][i][j];
		}
	}


	Z ans = sum[0][n][m] + sum[1][n][m];
	std::cout << ans << "\n";
}

G. Gleb and Boating

题意:你要从\(0\)走到\(s\)。初始方向为右,一次可以走\(k\)的距离,每次转向会使得\(k\)减一,最少为\(1\)。且每次转向就要移动一次,求走到\(s\)的最大\(k\)值。

这题主要是要发现当\(s > k^2\)时,答案为\(k\)\(k-2\)。当\(s \%k = 0\)时,答案为\(k\)。否则为\(k-2\)。证明如下:
我们可以先跳到\(k^2\)这个位置,然后看如果从这个位置跳\(k-2\)的话,大于等于\(s\)时会超过多少,记为\(r, 0 \leq r < k - 2\)。那么我们可以通过往回跳\(k-1\)抵消这个\(r\)。发现每次会使得\(r = (r + 1) \% (k - 2)\)。那么我们最多跳\(k - 2\)次。这显然不会跳过\(k^2\)
\(s \leq k^2\)的情况,我们直接\(bfs\)就行。

点击查看代码
void solve() {
    int s, k;
    std::cin >> s >> k;
    if (s % k == 0) {
    	std::cout << k << "\n";
    	return;
    }

    if (s > k * k) {
    	std::cout << std::max(1, k - 2) << "\n";
    	return;
    }

    std::vector<int> f(s + 1);
    f[k] = 1;
    int d = 1;
    auto bfs = [&]() -> void {
    	std::queue<int> q;
    	for (int i = 0; i <= s; ++ i) {
    		if (f[i]) {
    			q.push(i);
    		}
    	}

    	while (q.size()) {
    		int u = q.front(); q.pop();
    		if (u + d * k <= s && u + d * k>= 0 && !f[u + d * k]) {
    			f[u + d * k] = 1;
    			q.push(u + d * k);
    		}
    	}
    };

    while (true) {
    	bfs();
    	if (f[s]) {
    		std::cout << k << "\n";
    		return;
    	}

    	d = -d;
    	k = std::max(1, k - 1);
    	std::vector<int> g(s + 1);

    	for (int j = 0; j <= s; ++ j) {
    		if (f[j]) {
    			if (j + d * k >= 0 && j + d * k <= s) {
    				g[j + d * k] = 1;
    			}
    		}
    	}

    	f = g;
    }
}
posted @ 2025-03-26 01:13  maburb  阅读(410)  评论(1)    收藏  举报