A.Maximise The Score(贪心)
题意:
白板上写着 2 n 2n 2n 个正整数。你觉得无聊,决定用白板上的数字玩一个单人游戏。
开始时,你的分数为 0 0 0。你可以通过走下面的棋 n n n次来增加你的分数:
- 选择写在白板上的两个整数 x x x和 y y y。
- 将 min ( x , y ) \min(x,y) min(x,y)加到您的分数中。
- 擦去白板上的 x x x和 y y y。
注意,在走完 n n n步之后,白板上不会有任何整数。
如果你以最佳方式走完 n n n步棋,计算你最终能得到的最高分。
分析:
将数组从大到小排序,每次加分为两数中的最小值,能够发现数组中的最大值一定不会作为分数加入,若一次选择最大值与任意数,则可以将任意数加入分数,所以每次选择第二大的值与最大值一起被删除,重复此操作即为最优。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
int a[N];
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < 2 * n; i++) cin >> a[i];
sort(a, a + 2 * n);
int ans = 0;
for (int i = 0; i < n; i++) ans += a[2 * i];
cout << ans << endl;
}
return 0;
}
B.Permutation Printing(模拟)
题意:
给你一个正整数 n n n。
请找出一个长度为 n n n的排列 † ^\dagger †。要求长度为 n n n的排列 p p p中,不存在两个不同的索引 i i i和 j j j( 1 ≤ i , j < n 1\leq i,j \lt n 1≤i,j<n; i ≠ j i\neq j i=j),使得 p i p_i pi整除 p j p_j pj和 p i + 1 p_{i+1} pi+1整除 p j + 1 p_{j+1} pj+1。( 1 ≤ i , j < n 1\leq i,j \lt n 1≤i,j<n; i ≠ j i\neq j i=j)。
在此问题的限制条件下,可以证明至少存在一个 p p p。
† ^\dagger †长度为 n n n的排列是由 n n n个不同的整数组成的数组,这些整数从 1 1 1到 n n n依次排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是一个排列( 2 2 2出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是一个排列( n = 3 n=3 n=3,但数组中有 4 4 4)。
分析:
任意 > n 2 \gt \frac{n}{2} >2n的数字都不可能是任何数字的倍数。若我们大小交替排序,如果 a i a_i ai能够整除 a j a_j aj,那么一定会有 a i + 1 > a j + 1 a_{i+1}\gt a_{j+1} ai+1>aj+1,不可能满足整除,交替排序即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
int i = 1, j = n;
while (i < j) {
cout << i << ' ' << j << ' ';
i++;
j--;
}
if (i == j)
cout << i << ' ';
cout << endl;
}
return 0;
}
C.Lexicographically Largest(模拟)
题意:
史塔克有一个长度为 n n n的数组 a a a。他还有一个空集 S S S。注意, S S S并不是一个多重集。
他将进行以下三步操作恰好 n n n次:
- 选择一个序号 i i i,使得 1 ≤ i ≤ ∣ a ∣ 1\leq i\leq|a| 1≤i≤∣a∣。
- † ^\dagger †将 a i + i a_i+i ai+i插入 S S S。
- 从 a a a中删除 a i a_i ai。注意, a i a_i ai右边所有元素的索引将减少 1 1 1。
注意, n n n次操作后, a a a将为空。
现在,史塔克将构造一个新数组 b b b,这个数组以 S S S降序排列。形式上, b b b是一个大小为 ∣ S ∣ |S| ∣S∣的数组,其中 b i b_i bi是 S S S的第 i i i大元素( 1 ≤ i ≤ ∣ S ∣ 1\leq i\leq|S| 1≤i≤∣S∣)。
求史塔克操作后可以产生的字典序最大 ‡ ^\ddagger ‡的数组 b b b。
† ^\dagger †集合只能包含唯一的元素。插入一个已经存在于集合中的元素不会改变集合的元素。
‡ ^\ddagger ‡如果且仅当以下情况之一成立时,数组 p p p在字典序上大于序列 q q q:
- q q q是 p p p的前缀,但 p ≠ q p≠q p=q;
- 或者在数组 p p p和 q q q不同的第一个位置上,数组 p p p中有一个比 q q q中对应元素更大的元素。
注意, [ 3 , 1 , 4 , 1 , 5 ] [3,1,4,1,5] [3,1,4,1,5] 按字典序大于 [ 3 , 1 , 3 ] [3,1,3] [3,1,3]、 [ ] [\,] []和 [ 3 , 1 , 4 , 1 ] [3,1,4,1] [3,1,4,1],但不大于 [ 3 , 1 , 4 , 1 , 5 , 9 ] [3,1,4,1,5,9] [3,1,4,1,5,9]、 [ 3 , 1 , 4 , 1 , 5 ] [3,1,4,1,5] [3,1,4,1,5]和 [ 4 ] [4] [4] 。
分析:
题意为先确定所有数后再进行降序去重排序,而不是先移除的数一定在后移除的数之前。因为最后会进行排序,集合中的数越大越好,先选择前面的数后面索引 i i i会减小,所以先选后面的数字插入集合是最优的。
考虑集合要去重,如果数组 q q q是数组 p p p的前缀且 p ≠ q p≠q p=q那么 p p p的字典序大于 q q q。所以需要将相同的数字减少去重达到增大数组长度的目的。即对于相同的数字,优先取其前面的数,使其索引减少,如此最终集合的长度一定可以取到 n n n。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 3e5 + 10;
int a[N];
bool cmp(int x, int y) {
return x > y;
}
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] += i;
}
sort(a + 1, a + 1 + n, cmp);
for (int i = 2; i <= n; i++) {
a[i] = min(a[i], a[i - 1] - 1);
}
for (int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
return 0;
}
D.Sum over all Substrings(动态规划)
题意:
题目两个版本的唯一区别在于对 t t t和 n n n的限制。
对于长度均为 m m m的二进制 † ^\dagger †模式 p p p和二进制字符串 q q q,如果对于每个 i i i( 1 ≤ i ≤ m 1\leq i\leq m 1≤i≤m),都存在索引 l l l和 r r r满足下列要求,那么 q q q称为 p − g o o d p-good p−good:
- 1 ≤ l ≤ i ≤ r ≤ m 1\leq l\leq i\leq r\leq m 1≤l≤i≤r≤m
- p i p_i pi是字符串 q l , q l + 1 , … , q r q_l,q_{l+1},\ldots,q_{r} ql,ql+1,…,qr的模式 ‡ ^\ddagger ‡。
对于模式 p p p来说, f ( p ) f(p) f(p)是二进制字符串 p p p(长度与模式相同)中 1 \mathtt{1} 1个数的最小值。
给你一个大小为
n
n
n的二进制字符串
s
s
s。求
∑
i
=
1
n
∑
j
=
i
n
f
(
s
i
s
i
+
1
…
s
j
)
\sum_{i=1}^{n} \sum_{j=i}^{n} f(s_i s_{i+1} \ldots s_j)
i=1∑nj=i∑nf(sisi+1…sj)
即对 s s s的所有 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)子串中 f f f的值求和。
† ^\dagger †二进制模式是指仅由字符 0 \mathtt{0} 0和 1 \mathtt{1} 1组成的字符串。
‡ ^\ddagger ‡如果 t t t中 c c c的出现次数至少为 ⌈ m 2 ⌉ \lceil\frac{m}{2}\rceil ⌈2m⌉,则字符 c c c是长度为 m m m的字符串 t t t的模式。例如, 0 \mathtt{0} 0是 010 \mathtt{010} 010的模式, 1 \mathtt{1} 1 不是 010 \mathtt{010} 010的模式,而 0 \mathtt{0} 0和 1 \mathtt{1} 1都是 011010 \mathtt{011010} 011010的模式。
分析:
题意较难理解。 D 1 D1 D1的数据量可以通过暴力计算每一个子区间的答案解决。枚举所有 [ l , r ] [l,r] [l,r]。设 d p i dp_i dpi代表 1 − i 1-i 1−i所有前缀的答案。发现 010 010 010的结构可以把长度 3 3 3的覆盖。长度 2 2 2和长度 1 1 1差不多。因此 d p i dp_i dpi可以转移到 d p i − 1 dp_{i-1} dpi−1、 d p i − 2 dp_{i-2} dpi−2、 d p i − 3 dp_{i-3} dpi−3。
D 2 D2 D2枚举 r r r。发现除 [ r , r ] [r,r] [r,r]、 [ r − 1 , r ] [r-1,r] [r−1,r]以外,其他的一定是 [ r − 2 , r ] [r-2,r] [r−2,r]要在 d p dp dp中是最佳转移。因此可以进行优化。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 998244353;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
LL ans = 0;
vector<LL> dp(n + 1, 0);//坑点,使用数组初始化会超时
for (int i = 1; i <= n; i++) {
LL t = ans;
if (s[i] == '1') {
ans += s[i] - '0';
if (i >= 2)
ans += (s[i] - '0' + s[i - 1] - '0' + 1) / 2;
if (i >= 3)
ans += dp[i - 3] + (i - 2) * ((s[i] - '0' + s[i - 1] - '0' + s[i - 2] - '0' + 2) / 3);
} else {
ans += dp[i - 1];
}
dp[i] = ans - t;
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E.2…3…4… Wonderful! Wonderful!(数学)
题意:
史塔克有一个长度为 n n n的数组 a a a,其中对于所有 i i i( 1 ≤ i ≤ n 1\leq i\leq n 1≤i≤n), a i = i a_i=i ai=i。他将选择一个正整数 k k k( 1 ≤ k ≤ ⌊ n − 1 2 ⌋ 1\leq k\leq\lfloor\frac{n-1}{2}\rfloor 1≤k≤⌊2n−1⌋) 并对 a a a进行任意次数(可能是 0 0 0次)的以下运算。
- † ^\dagger †从 a a a中选择长度为 2 ⋅ k + 1 2\cdot k+1 2⋅k+1的子序列 s s s。他会从 a a a中删除 s s s的前 k k k个元素。为了保持完全平衡,他还将从 a a a中删除 s s s的最后 k k k个元素。
史塔克想知道对于每个 k k k( 1 ≤ k ≤ ⌊ n − 1 2 ⌋ 1\leq k\leq\lfloor\frac{n-1}{2}\rfloor 1≤k≤⌊2n−1⌋),最后能有多少个数组 a a a。由于史塔克不擅长计算问题,他需要你的帮助。
由于数组的数目可能太大,答案对 998244353 998244353 998244353取模。
† ^\dagger †如果 x x x可以通过删除几个(可能是零个或全部)元素从 y y y得到,那么序列 x x x就是序列 y y y的子序列。例如, [ 1 , 3 ] [1,3] [1,3]、 [ 1 , 2 , 3 ] [1,2,3] [1,2,3]和 [ 2 , 3 ] [2,3] [2,3]是 [ 1 , 2 , 3 ] [1,2,3] [1,2,3]的子序列。 [ 3 , 1 ] [3,1] [3,1]和 [ 2 , 1 , 3 ] [2,1,3] [2,1,3]不是 [ 1 , 2 , 3 ] [1,2,3] [1,2,3]的子序列。
分析:
需要找出可能的 b b b个数。可以求出长度为 n n n的二进制字符串 s s s的个数,如果 i i i出现在 b b b中,则 s i = 1 s_i=1 si=1,否则 s i = 0 s_i=0 si=0。
对于给定的 n n n和 k k k,如果存在可以从 a a a得到 b b b,我们称 s s s为好字符串。
对于本题,与其计算好的字符串 s s s,不如计算不坏的字符串的数量。
为了方便起见,只考虑 0 \mathtt{0} 0的个数能被 2 k 2k 2k整除的字符串 s s s 。
当且仅当在 s s s中从左边开始的 k k k个 0 0 0和从右边开始的 k k k个 0 0 0之间不存在任何 1 1 1时, s s s是坏的。
把左边的第 k k k个 0 0 0和右边的第 k k k个 0 0 0之间的所有 0 \mathtt{0} 0压缩成一个 0 0 0,并称这个新字符串为 t t t。需要注意的是, t t t中将包含 2 k − 1 2k-1 2k−1个 0 \mathtt{0} 0。 0 \mathtt{0} 0's。我们还可以观察到,对于每一个 t t t,都存在一个唯一的 s s s。因为 n n n和 k k k已经被固定。因此,恰好有 x x x个 1 \mathtt{1} 1的坏字符串 s s s的数量是 ( x + 2 k − 1 2 k − 1 ) {{x+2k-1}\choose{2k-1}} (2k−1x+2k−1) ,因为恰好有 ( x + 2 k − 1 2 k − 1 ) {{x+2k-1}\choose{2k-1}} (2k−1x+2k−1)个二进制字符串 t t t含有 2 k − 1 2k-1 2k−1个 0 \mathtt{0} 0以及 x x x个 1 \mathtt{1} 1。
最后,恰好有 ( n x ) − ( x + 2 k − 1 2 k − 1 ) \binom{n}{x}-\binom{x+2k-1}{2k-1} (xn)−(2k−1x+2k−1)个好的二进制字符串 s s s具有 x x x个 1 \mathtt{1} 1个和 n − x n-x n−x个 0 \mathtt{0} 0。不需要为从 1 1 1到 n n n的每个 x x x找出这个值,因为 s s s中 0 \mathtt{0} 0的个数( n − x n-x n−x)应该是 2 k 2k 2k的倍数。 x x x中只有 O ( n 2 k ) O(\frac{n}{2k}) O(2kn)是有用的候选数。复杂度 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 1e6 + 10;
LL fac[N], inv[N];
LL pw(LL x, LL y) {
LL res = 1;
while (y) {
if (y & 1) {
res = res * x % mod;
}
x = x * x % mod;
y >>= 1;
}
return res;
}
LL Inv(LL x) {
return pw(x, mod - 2);
}
void init() {
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = Inv(fac[i]);
}
}
LL C(LL x, LL y) {
if (x < 0 || y < 0 || x < y)return 0;
return fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int main() {
init();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int k = 1; k * 2 < n; k++) {
LL ans = 1;
for (int t = 1; 2 * k * t < n; t++) {
ans = (ans + C(n, 2 * k * t) - C(n - 2 * k * (t - 1) - 1, 2 * k - 1) + mod) % mod;
}
cout << ans << " ";
}
cout << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。