A.Strange Splitting(思维)
题意:
将非空数组的范围定义为最大值减去最小值。例如, [ 1 , 4 , 2 ] [1,4,2] [1,4,2]的范围是 4 − 1 = 3 4-1=3 4−1=3。
给你一个长度为 n ≥ 3 n\geq 3 n≥3的数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an。保证 a a a被排序。
你必须给 a a a中的每个元素涂上红色或蓝色,以便:
- 红色元素的范围不等于蓝色元素的范围,并且
- 每种颜色至少有一个元素。
如果不存在这样的着色,则输出 N O NO NO。如果存在多种有效着色,则可以输出其中任何一种。
分析:
如果 a 1 = a n a_1=a_n a1=an,无解。
如果 a 2 = a n a_2=a_n a2=an, a 1 , a 2 a_1,a_2 a1,a2涂成红色,否则只把 a 1 a_1 a1涂成红色。
代码:
#include<bits/stdc++.h>using namespace std;
const int N = 1e6 + 10;
int a[N];void solve() {int n;cin >> n;for (int i = 1; i <= n; ++i)cin >> a[i];if (a[1] == a[n]) {cout << "NO" << endl;return;}cout << "YES" << endl;if (a[2] != a[n]) {cout << "R";for (int i = 2; i <= n; ++i) {cout << "B";}cout << endl;return;}cout << "RR";for (int i = 3; i <= n; ++i) {cout << "B";}cout << endl;
}int main() {int t;cin >> t;while (t--)solve();return 0;
}
B.Large Addition(模拟)
题意:
如果一个数位介于 5 5 5和 9 9 9之间(含),则该数位为大数。如果一个正整数的所有位数都是大数,那么这个正整数就是大正整数。
给你一个整数 x x x。它可以是两个位数相同的大正整数之和吗?
分析:
由于大于等于 5 5 5小于等于 9 9 9的数不管这么组合,两个数字相加的和一定在 10 − 18 10-18 10−18之间,那么可以得到如果给出的数的第一位上不是 1 1 1或者最后一位上是 9 9 9或者或者中间有一位上是 0 0 0,那么就是不存在的,否则就是可行的。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;void solve() {LL x;cin >> x;bool flag = 1;if (x % 10 == 9)flag = 0;x /= 10;while (x) {int y = x % 10;if ((y + 9) % 10 == 9)flag = 0;if (x < 10 && y > 1)flag = 0;x /= 10;}if (flag)cout << "YES" << endl;elsecout << "NO" << endl;
}int main() {int t;cin >> t;while (t--)solve();return 0;
}
C1.Magnitude (Easy Version)(贪心)
题意:
两个版本的问题是不同的。
给你一个长度为 n n n的数组 a a a。从 c = 0 c=0 c=0开始。然后,对于从 1 1 1到 n n n的每一个 i i i(按递增顺序)做下面的操作之一:
- 选项 1 1 1: 设置 c c c至 c + a i c+a_i c+ai。
- 选项 2 2 2: 设置 c c c至 ∣ c + a i ∣ |c+a_i| ∣c+ai∣, ∣ x ∣ |x| ∣x∣是 x x x的绝对值。
设上述步骤后 c c c的最大终值等于 k k k。求出 k k k。
分析:
发现要么一直不用 2 2 2操作,要么在答案取到最小值的时候使用一遍 2 2 2操作让其变为相对大一些的值。显然使用多遍 2 2 2操作是不优的。然后还能发现若存在一个位置 P P P使得前缀和小于 0 0 0,那么这个地方实验 2 2 2操作较优。因此若不存在一个前缀和的值小于 0 0 0那么就不应该使用 2 2 2操作。否则一定在任意一个值最小的位置使用一次 2 2 2操作即可。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;
const int N = 1e6 + 10;
LL a[N], pre[N];void solve() {LL n, id = -1;cin >> n;for (LL i = 1; i <= n; i++) {cin >> a[i];pre[i] = pre[i - 1] + a[i];}LL mi = 0;for (LL i = n; i; i--) {if (pre[i] < mi) {mi = pre[i];id = i;}}if (id == -1)cout << pre[n] << endl;else {LL s = 0;for (LL i = 1; i <= n; i++) {if (i <= id)s -= a[i];elses += a[i];}cout << s << endl;}
}int main() {int t;cin >> t;while (t--)solve();return 0;
}
C2.Magnitude (Hard Version)(贪心)
题意:
两个版本的问题是不同的。
给你一个长度为 n n n的数组 a a a。从 c = 0 c=0 c=0开始。然后,对于从 1 1 1到 n n n的每个 i i i(按递增顺序),做以下操作之一:
- 选项 1 1 1:设置 c c c至 c + a i c+a_i c+ai。
- 选项 2 2 2:设置 c c c至 ∣ c + a i ∣ |c+a_i| ∣c+ai∣, ∣ x ∣ |x| ∣x∣是 x x x的绝对值。
假设经过上述程序后, c c c的最大终值等于 k k k。求得出 c = k c=k c=k的唯一程序的个数。如果在任意索引 i i i处,一个程序选择了选项 1 1 1,而另一个程序选择了选项 2 2 2,即使 c c c的值在该轮之后对两个程序都相等,那么这两个程序也是不同的。
由于答案可能很大,输出对 998244353 998244353 998244353取模的结果。
分析:
每一步结束之后,若当前的前缀和取到了全部前缀和的最小值,则这一步对答案造成了 2 n − i + r 2^{n-i+r} 2n−i+r贡献,其中 r r r是在这一步操作中前缀和不小于 0 0 0的位置的数量。若全部操作中没有任何一个前缀和小于 0 0 0则答案为 2 n 2^n 2n。
不存在任何一个前缀和使得这个前缀和不大于 0 0 0。此时对于任意一个位置 1 1 1操作和 2 2 2操作没有任何本质区别。任意一步都可以在 1 1 1操作和 2 2 2操作中任选一个操作执行。
存在一个前缀和使得这个前缀和不大于 0 0 0。因为执行完这一步之后后面不论怎么走答案都不会偏离最大值(参见 C 1 C1 C1的结论),所以说后面的任意一步都可以在 1 1 1操作和 2 2 2操作中任选,即 2 n − i 2^{n-i} 2n−i种不同选择;若前面有前缀和大于等于 0 0 0的那么 1 1 1操作和 2 2 2操作没有本质的区别,若有 r r r个满足这样条件的则有 2 r 2^r 2r种不同选择。根据乘法原理可知对答案的贡献为 2 n − i + r 2^{n-i+r} 2n−i+r。其中 r r r可以在枚举的时候扫一遍得出答案。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;
const int N = 1e6 + 10;
const int MOD = 998244353;
LL a[N], pre[N], bin[N];void solve() {int n;cin >> n;for (int i = 1; i <= n; i++) {cin >> a[i];pre[i] = pre[i - 1] + a[i];}LL mi = 0, r = 0;for (int i = 1; i <= n; i++)mi = min(mi, pre[i]);if (mi == 0)cout << bin[n] << endl;else {int ans = 0;for (int i = 1; i <= n; i++) {if (pre[i] >= 0)r++;if (pre[i] == mi)ans = (ans + bin[n - i + r]) % MOD;}cout << ans << endl;}
}int main() {int t;cin >> t;bin[0] = 1;for (int i = 1; i < N; i++)bin[i] = bin[i - 1] * 2 % MOD;while (t--)solve();return 0;
}
D.“a” String Problem(数学)
题意:
给你一个由小写拉丁字符组成的字符串 s s s。请计算非空字符串 t ≠ t\neq t=" a \texttt{a} a"的个数。使得可以将 s s s分割为满足以下条件的一些子字符串:
- 每个子串要么等于 t t t要么等于" a \texttt{a} a",并且
- 至少有一个子串等于 t t t。
† ^{\dagger} †字符串 s s s的分区是由一些 k k k字符串 t 1 , t 2 , … , t k t_1,t_2,\ldots,t_k t1,t2,…,tk(称为子串)组成的有序序列,使得 t 1 + t 2 + … + t k = s t_1+t_2+\ldots+t_k=s t1+t2+…+tk=s,其中 + + +表示连接操作。
分析:
设 s s s长度为 n n n。特判全为 a a a的情况,共 n − 1 n−1 n−1种方案。
设 s s s中非 a a a字符的数量为 m m m。
如果 t t t中有 k k k个非 a a a字符,把 t t t掐头去尾忽略前后的 a a a后,原串里一定恰好存在 m k \frac{m}{k} km个 t ′ t′ t′,且 k ∣ m k∣m k∣m。
枚举 k k k。记录第 i i i个非 a a a字符的位置为 p i p_i pi(从 0 0 0开始),如果 s p i ≠ s p i − k s_{p_i} \neq s_{p_i−k} spi=spi−k或不在分割处的 p i − p i − 1 ≠ p i − k − p i − k − 1 p_i−p_{i−1}\neq p_{i−k}−p_{i−k−1} pi−pi−1=pi−k−pi−k−1, t ′ t′ t′不合法。
把 t ′ t′ t′还原为 t t t,枚举左边有的 a a a个数 l ∈ [ 0 , p 0 ] l∈[0,p_0] l∈[0,p0]。
设 x x x为分割处的最小间隔 m i n ( p i − p i − 1 ) min(p_i−p_{i−1}) min(pi−pi−1), k ∣ i k∣i k∣i,则右边的 a a a个数 r ∈ [ 0 , m i n ( x − l , n − p m − 1 − 1 ) ] r∈[0,min(x−l,n−p_{m−1}-1)] r∈[0,min(x−l,n−pm−1−1)]。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;
const int MOD = 998244353;void solve() {string s;cin >> s;int n = s.length();if (count(s.begin(), s.end(), 'a') == n) {cout << n - 1 << endl;return;}vector<int> a;for (int i = 0; i < n; ++i) {if (s[i] != 'a') {a.emplace_back(i);}}int m = a.size();LL ans = 0;for (int i = 1; i <= m; ++i) {if (m % i) {continue;}int ok = 1;for (int j = i; j < m; ++j) {int o = j % i;if (s[a[j]] != s[a[o]] || (o && a[o] - a[o - 1] != a[j] - a[j - 1])) {ok = 0;break;}}if (ok) {int mi = n;for (int j = i; j < m; j += i) {mi = min(mi, a[j] - a[j - 1] - 1);}int r = n - a.back() - 1;for (int l = 0; l <= a[0]; ++l) {ans += max(0, min(r + 1, mi - l + 1));}}}cout << ans << endl;
}int main() {int t;cin >> t;while (t--)solve();return 0;
}
E.Shuffle(树形DP)
题意:
两只饥饿的小熊猫奥斯卡和卢拉有一棵有 n n n个节点的树 T T T。它们愿意在整棵树 T T T上精确地执行一次下面的洗牌程序。通过这个洗牌过程,他们将从旧树的节点中创建一棵新树。
- 从原始树 T T T中选择任意一个节点 V V V。创建一棵以 V V V为根的新树 T 2 T_2 T2。
- 从 T T T中移除 V V V,这样原树就会分裂成一个或多个子树(如果 V V V是 T T T中的唯一节点,则子树数量为零)。
- 用同样的方法对每棵子树进行洗牌(同样选择任意节点作为根节点),然后将所有洗牌后子树的根节点连接回 V V V以完成 T 2 T_2 T2的构建。
这样,奥斯卡和卢拉就得到了一棵新树 T 2 T_2 T2。它们只能吃树叶,而且非常饥饿,因此请找出在所有树中,只需一次洗牌就能生成的树叶的最大数量。
请注意,树叶是阶数为 1 1 1的所有节点。因此,如果树根只有一个子节点,那么它就可以被视为树叶。
分析:
把一个点的父亲儿子都加到 T 2 T_2 T2后,这个点就是叶子,且与之直接相连的点都不是叶子。(所有相邻点都是这个点的祖先)。
所有的叶子构成一个独立集,我们找去除根后最大的。
枚举每个节点作为初始的根,换根进行 d p dp dp。如果根本身就是叶子还要在加一。
具体来说,第一次扫描以 1 1 1为根求出 f x , 1 / 0 f_{x,1/0} fx,1/0表示以 x x x为根的子树中 x x x选/不选的最大独立集:
{ f x , 0 = ∑ y ∈ g x max ( f y , 0 , f y , 1 ) f x , 1 = ∑ y ∈ g x f y , 0 \begin{cases}f_{x,0}=\sum_{y\in g_x}\max(f_{y,0},f_{y,1})\\f_{x,1}=\sum_{y\in g_x}f_{y,0}\end{cases} {fx,0=∑y∈gxmax(fy,0,fy,1)fx,1=∑y∈gxfy,0
第二次扫描求出 h x , 1 / 0 h_{x,1/0} hx,1/0表示选/不选 x x x的最大独立集,
h 1 , 0 / 1 = f 1 , 0 / 1 h_{1,0/1}=f_{1,0/1} h1,0/1=f1,0/1, { h x , 0 = max ( h f a , 1 , f x , 0 + ( h f a , 0 − max ( f x , 1 , f x , 0 ) ) ) h x , 1 = f x , 1 + ( h f a , 0 − max ( f x , 1 , f x , 0 ) ) \begin{cases}h_{x,0}=\max(h_{fa,1},f_{x,0}+(h_{fa,0}-\max(f_{x,1},f_{x,0})))\\h_{x,1}=f_{x,1}+(h_{fa,0}-\max(f_{x,1},f_{x,0}))\end{cases} {hx,0=max(hfa,1,fx,0+(hfa,0−max(fx,1,fx,0)))hx,1=fx,1+(hfa,0−max(fx,1,fx,0))
用 h x , 0 + [ x is leaf ] h_{x,0}+[x \texttt{ is leaf}] hx,0+[x is leaf]更新答案。
代码:
#include<bits/stdc++.h>using namespace std;
const int MOD = 998244353;void solve() {int n;cin >> n;vector<vector<int>> g(n + 1);for (int i = 1; i < n; ++i) {int x, y;cin >> x >> y;g[x].emplace_back(y);g[y].emplace_back(x);}vector<array<int, 2>> f(n + 1, {0, 0});auto dfs = [&](auto &&dfs, int x, int fa) -> void {f[x][0] = 0;f[x][1] = 1;for (int y: g[x]) {if (y != fa) {dfs(dfs, y, x);f[x][0] += max(f[y][0], f[y][1]);f[x][1] += f[y][0];}}};dfs(dfs, 1, 0);vector<array<int, 2>> h(n + 1);h[1][0] = f[1][0];h[1][1] = f[1][1];int ans = h[1][0] + (g[1].size() == 1);auto dfs2 = [&](auto &&dfs2, int x, int fa) -> void {int tmp = h[fa][0] - max(f[x][0], f[x][1]);h[x][1] = f[x][1] + tmp;h[x][0] = max(h[fa][1], f[x][0] + tmp);tmp = h[x][0] + (g[x].size() == 1);ans = max(ans, tmp);for (int y: g[x]) {if (y != fa) {dfs2(dfs2, y, x);}}};for (int y: g[1]) {dfs2(dfs2, y, 1);}cout << ans << endl;
}int main() {int t;cin >> t;while (t--)solve();return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。