A.Painting the Ribbon (思维)
题意:
给出 n n n种物品,需要用 m m m种颜色进行染色,使得:
- 不存在选择 k k k个物品,将它们染成同一颜色之后,会使得所有物品颜色一致的方案。
询问能否实现。
分析:
一种颜色的数量不能超过 ( n − k − 1 ) (n-k-1) (n−k−1),那么最多可以染色 m × ( n − k − 1 ) m \times (n-k-1) m×(n−k−1)样物品,并且数量需要大于等于 n n n。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n, m, k;
cin >> n >> m >> k;
if ((n - k - 1) * m >= n) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
return 0;
}
B.Make It Ugly (思维)
题意:
把一个数组 a a a 称为 "美丽"数组,当且仅当能通过任意次数(可能是零)的以下操作使数组中的所有元素都相同:
- 选择一个索引 i i i ( 2 ≤ i ≤ ∣ a ∣ − 1 2 \le i \le |a| - 1 2≤i≤∣a∣−1 ),例如 a i − 1 = a i + 1 a_{i - 1} = a_{i + 1} ai−1=ai+1 ,然后将 a i a_i ai 替换为 a i − 1 a_{i - 1} ai−1 。
给出一个漂亮的数组 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an 。要使数组不再美丽,至少要删除多少个元素?禁止交换元素。如果不可能做到,那么输出 − 1 -1 −1。
分析:
如果数组中所有的数字都相同直接输出 − 1 -1 −1,因为无法更改开头和结尾。计算和 a [ 0 ] a[0] a[0]值相同的连续段的最小长度,那么就可以构造出两个与开头结尾不一样的数来构成一个不美丽序列。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 0; i < n; i++)
cin >> a[i];
bool flag = 1;
for (int i = 1; i < n; i++)
if (a[i] != a[0])
flag = 0;
if (flag) {
cout << -1 << endl;
continue;
}
int cnt = 0;
int ans = 1e9;
for (int i = 0; i < n; i++) {
if (a[i] == a[0])
cnt++;
else
ans = min(ans, cnt), cnt = 0;
}
if (cnt)
ans = min(ans, cnt);
cout << ans << endl;
}
return 0;
}
C.Long Multiplication (贪心)
题意:
给出两个长度相同的整数
x
x
x 和
y
y
y ,由
1
1
1 到
9
9
9 之间的数字组成。
可以执行以下操作任意多次(可能为零):交换
x
x
x 中的
i
i
i 个位数和
y
y
y 中的
i
i
i 个位数。
例如,如果
x
=
73
x=73
x=73 和
y
=
31
y=31
y=31 中的
2
2
2 个位数对调,就可以得到
x
=
71
x=71
x=71 和
y
=
33
y=33
y=33。
你的任务是可以使用任意次上述操作,最大化
x
x
x 和
y
y
y 的乘积。如果有多个答案,请输出任意一个。
分析:
因为 x + y x+y x+y为定值,所以 x x x和 y y y的差值越小,积越大。令最终 x ≤ y x\le y x≤y,那么我们逐位判断,如果前面的位数已经推出 x > y x>y x>y,那么尽可能交换后面的位数使得 y y y变大。如果之前位数只能推出 x = y x=y x=y,那么根据当前位数判断是否能进行交换。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
string s1, s2;
cin >> s1 >> s2;
int n = s1.length();
int flag = 0;
for (int i = 0; i < n; i++) {
if (flag == 0) {
if (s1[i] < s2[i])
swap(s1[i], s2[i]);
if (s1[i] > s2[i])
flag = 1;
} else {
if (s1[i] > s2[i]) {
swap(s1[i], s2[i]);
}
}
}
cout << s1 << endl << s2 << endl;
}
return 0;
}
D.Colored Balls (dp)
题意:
有 n n n 种颜色的球,其中 i i i 种颜色的球的数量是 a i a_i ai 。
这些球可以组合成一组。每组最多包含 2 2 2 个球,每种颜色的球不超过 1 1 1 个。
考虑所有 2 n 2^n 2n 组颜色。对于一组颜色,让我们把它的值表示为这些颜色的球所能分配到的最少组数。例如,如果有三种颜色,分别有 3 3 3 、 1 1 1 和 7 7 7 个球,它们可以组合成 7 7 7 组(且不少于 7 7 7 ),那么这组颜色的值就是 7 7 7 。
你的任务是计算所有 2 n 2^n 2n 种可能的颜色组的值之和。并将答案对 998 244 353 998\,244\,353 998244353 取模。
分析:
将问题转化成:有若干个小球,每个小球具备某种颜色,不同颜色的可以匹配,问最少的分组数。那么我们设最多颜色的小球有
m
x
mx
mx个,总数有
n
n
n个,如果
m
x
mx
mx超过
n
n
n的一半,那最小分组数就是
m
x
mx
mx个,否则就是
⌈
n
2
⌉
\lceil \frac{n}{2} \rceil
⌈2n⌉个。
我们将各种颜色小球的数量排序,然后枚举数量最多的小球有多少个。用背包统计在这之前选择了
x
x
x个小球的方案数,计算答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 998244353;
int main() {
int T;
T = 1;
while (T--) {
int n;
cin >> n;
vector<LL> a(n);
int m = 0;
for (int i = 0; i < n; i++)
cin >> a[i], m += a[i];
sort(a.begin(), a.end());
vector<LL> dp(m + 1, 0);
dp[0] = 1;
LL ans = 0;
for (auto x: a) {
for (int j = 0; j <= m; j++) {
if (j <= x) {
ans += x * dp[j];
ans %= mod;
} else {
ans += ((x + j + 1) / 2) * dp[j];
ans %= mod;
}
}
for (int j = m; j >= x; j--) {
dp[j] += dp[j - x];
dp[j] %= mod;
}
}
cout << ans << endl;
}
return 0;
}
E. Chain Reaction (数据结构)
题意:
一排站着 n n n 只怪物。第 i i i 个怪物的生命值是 a i a_i ai 。
每隔一秒,你可以选择一只活着的怪物,向它发射一道连锁闪电。闪电会对它造成 k k k 伤害,同时也会向左(向 i i i 递减)和向右(向 i i i 递增)扩散到只活着的怪物身上,对每只怪物造成 k k k 伤害。当闪电到达死亡怪物或行首/行尾时,就会停止。如果怪物的生命值严格大于 0 0 0 ,则视为存活。
例如,考虑以下情况:有三个怪物的健康值分别等于 [ 5 , 2 , 7 ] [5, 2, 7] [5,2,7] 和 k = 3 k = 3 k=3 。你可以在 4 4 4 秒内将它们全部杀死:
- 向 3 t h 3th 3th 的怪物发射连锁闪电,那么它们的生命值为 [ 2 , − 1 , 4 ] [2, -1, 4] [2,−1,4] ;
- 向 1 t h 1th 1th 怪物发射连锁闪电,则它们的生命值为 [ − 1 , − 1 , 4 ] [-1, -1, 4] [−1,−1,4] ;
- 向 3 t h 3th 3th 怪物发射连锁闪电,则它们的生命值为 [ − 1 , − 1 , 1 ] [-1, -1, 1] [−1,−1,1] ;
- 向 3 t h 3th 3th 怪物发射连锁闪电,则它们的生命值为 [ − 1 , − 1 , − 2 ] [-1, -1, -2] [−1,−1,−2] 。
从 1 1 1 到 max ( a 1 , a 2 , … , a n ) \max(a_1, a_2, \dots, a_n) max(a1,a2,…,an) 的每个 k k k ,计算杀死所有怪物所需的最少秒数。
分析:
上述操作等价于每次选择不含
0
0
0的一段,让段里每个数字都减去
1
1
1,求出原序列的差分序列,使得操作等价于选择两个数字
(
x
,
y
)
(x,y)
(x,y),让
d
x
d_x
dx加一,
d
y
d_y
dy减一。
通过观察发现。在差分序列中,如果出现一个负数,那么之前一定出现了对应数量的正数,所以我们可以选择一个正数和负数进行配对,就可以删掉一个
1
1
1 和一个
−
1
-1
−1。所以操作数等于差分序列中正数之和。同时可以发现相邻元素的大小关系不会改变,所以我们用整除分块枚举每个位置可能的取值,用差分静态前缀和更新答案。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int T;
T = 1;
while (T--) {
int n;
cin >> n;
vector<int> a(n + 2);
int maxval = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
maxval = max(maxval, a[i]);
}
const int m = maxval;
vector<LL> d(m + 2);
for (int i = 1; i <= n; i++) {
int x = a[i];
for (int l = 1, r; l < x; l = r + 1) {
int tmp = (x + l - 1) / l;
r = (x + tmp - 2) / (tmp - 1) - 1;
if (a[i] >= a[i - 1]) {
d[l] += tmp;
d[r + 1] -= tmp;
}
if (a[i] <= a[i + 1]) {
d[l] -= tmp;
d[r + 1] += tmp;
}
}
if (a[i] <= a[i + 1]) {
d[x] -= 1;
}
if (a[i] >= a[i - 1]) {
d[x] += 1;
}
}
for (int i = 1; i <= m; i++) {
d[i] += d[i - 1];
cout << d[i] << " ";
}
cout << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。