五一快乐 (~ ̄▽ ̄)~
比赛链接:https://codeforces.com/contest/1519
A. Red and Blue Beans
题意
有 (r) 个红色豆子和 (b) 个蓝色豆子,需要将它们分成几包,要求每包:
- 至少有一个红色豆子
- 至少有一个蓝色豆子
- 两种颜色豆子的数量之差不超过 (d)
问能否将所有豆子分完 ?
题解
假设红色豆子比蓝色豆子少,为了尽可能地将蓝色豆子分完,每包应该放进 (1) 个红色豆子, (1 + d) 个蓝色豆子,否则无解。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int r, b, d;
cin >> r >> b >> d;
if (r > b) {
swap(r, b);
}
cout << (1LL * r * (d + 1) >= b ? "YES" : "NO") << "
";
}
return 0;
}
B. The Cake Is a Lie
题意
有一个 (n imes m) 的方阵,假设当前在 ((x, y)) 点,可以从以下两种移动操作中选择一种:
- 向右移动到 ((x, y + 1)) ,花费为 (x)
- 向下移动到 ((x + 1, y)) ,花费为 (y)
起点为 ((1, 1)) ,问抵达终点 ((n, m)) 的花费能否刚好为 (k) 。
题解
花费只与起点与终点有关,与路径无关,即只需判断 (k) 是否等于从 ((1, 1)) 到 ((n, m)) 的花费即可。
证明
假设有一个 (2 imes 2) 的方阵,从左上角 ((x, y)) 到右下角 ((x + 1, y + 1)) 显然只有两条路径,易得两条路径的花费均为 (x + y + 1) 。
即,在一个 (2 imes 2) 方阵中,路径是可以关于主对角线翻折的。
那么对于任意起点到任意终点间的任意路径,都可以利用路径中折角所在的 (2 imes 2) 方阵将路径翻折至同一种。
从 ((1, 1)) 到 ((n, m)) 最简单的路径即直右直下(或直下直右),花费即 ((m - 1) imes 1 + (n - 1) imes m = n imes m - 1) 。
注:更完善的证明可以参考官方题解中的归纳证明法。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n, m, k;
cin >> n >> m >> k;
cout << (k == n * m - 1 ? "YES" : "NO") << "
";
}
return 0;
}
C. Berland Regional
题意
有 (n) 个学生,他们分别来自大学 (u_i) 并且编程能力为 (s_i) 。
当队伍人数为 (k) 时,每个大学会以 (s_i) 为依据从大到小成 (k) 个将学生尽可能多地组队派出。
问当 (k) 的值为 (1,2,...,n) 时,每次对应的所有队伍的编程能力之和为多少。
题解
对于学生人数为 (size) 的大学,当 (k > size) 时无法再派出队伍,所以该大学只需考虑 (k in [1, size]) 的情况。
当 (k) 为某个值时,该大学的后 (size \% k) 个学生无法组队派出,即 (ans[k] mathrel{+}= s_0 + s_1 + dots + s_{size - 1 - (size \% k)}) ,多次对连续区间求和可以使用前缀和预处理。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> u(n);
for (int i = 0; i < n; i++) {
cin >> u[i];
--u[i];
}
vector<int> s(n);
for (int i = 0; i < n; i++) {
cin >> s[i];
}
vector<vector<long long>> a(n);
for (int i = 0; i < n; i++) {
a[u[i]].push_back(s[i]);
}
vector<long long> ans(n + 1);
for (auto vec : a) {
sort(vec.begin(), vec.end(), greater<>());
for (int i = 1; i < (int)vec.size(); i++) {
vec[i] += vec[i - 1];
}
for (int k = 1; k <= (int)vec.size(); k++) {
ans[k] += vec[(int)vec.size() - 1 - (int)vec.size() % k];
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << "
"[i == n];
}
}
return 0;
}
D. Maximum Sum of Products
题意
给出两个大小为 (n) 的数组 (a、b) ,最多可以反转一次 (a) 中的连续子数组,问 (sum_{i = 1}^{n} limits a_i cdot b_i) 的最大值。
题解
枚举中心点,依次向两边扩展。
Tips
注意连续子数组长度分奇偶两种情况。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<long long> b(n);
for (int i = 0; i < n; i++) {
cin >> b[i];
}
long long ans = 0;
for (int i = 0; i < n; i++) {
ans += a[i] * b[i];
}
long long max_delta = 0;
for (int i = 0; i < n; i++) {
long long delta = 0;
for (int l = i - 1, r = i + 1; l >= 0 and r < n; --l, ++r) {
delta -= a[l] * b[l] + a[r] * b[r];
delta += a[l] * b[r] + a[r] * b[l];
max_delta = max(max_delta, delta);
}
delta = 0;
for (int l = i, r = i + 1; l >= 0 and r < n; --l, ++r) {
delta -= a[l] * b[l] + a[r] * b[r];
delta += a[l] * b[r] + a[r] * b[l];
max_delta = max(max_delta, delta);
}
}
cout << ans + max_delta << "
";
return 0;
}