A. Special Characters (思维)
题意:
给你一个整数
n
n
n 。构造一个由大写字母组成的字符串。这个字符串中必须有
n
n
n个特殊字符。如果一个字符与其相邻的一个字符相等,我们就将其称为特殊字符。
例如,在 AAABAACC
字符串中有
6
6
6 个特殊字符(分别位于
1
1
1 、
3
3
3 、
5
5
5 、
6
6
6 、
7
7
7 和
8
8
8 )。
输出任何满足题意的字符串。
分析:
奇数情况无解。偶数情况通过 A A B B AABB AABB循环的形式进行构造。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n & 1)
cout << "NO" << endl;
else {
cout << "YES" << endl;
string ans;
for (int i = 1, j = 1; i <= n; i += 2, j++) {
if (j & 1)
ans += "AA";
else
ans += "BB";
}
cout << ans << endl;
}
}
return 0;
}
B.Array Fix (贪心)
题意:
给你一个长度为 n n n 的整数数组 a a a 。
你可以执行以下操作任意多次(可能为零):取数组 a a a 中至少是 10 10 10 的任意元素,删除它,然后在相同位置插入该元素包含的数字,按它们在该元素中出现的顺序排列。
- 如果我们对数组 [ 12 , 3 , 45 , 67 ] [12, 3, 45, 67] [12,3,45,67] 中的 第 3 3 3个元素执行此操作,那么数组就变成了 [ 12 , 3 , 4 , 5 , 67 ] [12, 3, 4, 5, 67] [12,3,4,5,67] 。
- 如果我们对数组 [ 2 , 10 ] [2, 10] [2,10] 中的 第 2 2 2个元素执行此操作,那么数组就变成了 [ 2 , 1 , 0 ] [2, 1, 0] [2,1,0] 。
你的任务是确定是否有可能通过任意次上述操作使 a a a 以非降序排序。换句话说,你必须确定是否有可能将数组 a a a 转换为 a 1 ≤ a 2 ≤ ⋯ ≤ a k a_1 \le a_2 \le \dots \le a_k a1≤a2≤⋯≤ak ,其中 k k k 是数组 a a a 的当前长度。
分析:
我们从后往前倒推,记录前一个的值,如果比当前值大就尝试拆当前值。拆了值只会变小,所以尽可能不拆。
代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int a[105];
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
bool flag = 1;
int r = INF;
for (int i = n - 1; i >= 0 && flag; i--) {
int x = a[i];
if (a[i] <= r)
r = a[i];
else {
while (x) {
int y = x % 10;
if (y <= r)
r = y;
else {
flag = 0;
break;
}
x /= 10;
}
}
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
C. Arrow Path (bfs)
题意:
有一个网格,由 2 2 2 行和 n n n 列组成。行的编号从上到下从 1 1 1 到 2 2 2 。列的编号从左到右依次为 1 1 1 至 n n n 。网格的每个单元格都包含一个箭头,指向左边或右边。没有箭头指向网格外。
有一个机器人从 ( 1 , 1 ) (1, 1) (1,1) 格开始。每隔一秒钟,下面两个动作会相继发生:
- 首先,机器人向左、向右、向下或向上移动(不能试图移动到网格外,也不能跳过移动);
- 然后,机器人沿着放置在当前单元格中的箭头移动。
判断机器人能否到达 ( 2 , n ) (2, n) (2,n) 单元格。
分析:
b f s bfs bfs的过程中,将每一步加入队列的过程改成每两步加入一次队列即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
string mp[2];
int vis[2][N], dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
unordered_map<char, int> dir;
int main() {
int t;
cin >> t;
dir['<'] = 3;
dir['>'] = 1;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < 2; i++) {
cin >> mp[i];
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < n; j++) {
vis[i][j] = 0;
}
}
vis[0][0] = 1;
queue<pair<LL, LL> > q;
q.push({0, 0});
bool flag = false;
while (q.size()) {
auto tmp = q.front();
q.pop();
int x = tmp.first, y = tmp.second;
if (x == 1 && y == n - 1) {
flag = 1;
break;
}
for (int i = 0; i < 4; i++) {
int X = x + dx[i], Y = y + dy[i];
if (X < 0 || X > 1 || Y < 0 || Y > n - 1)
continue;
int X2 = X + dx[dir[mp[X][Y]]], Y2 = Y + dy[dir[mp[X][Y]]];
if (X2 < 0 || X2 > 1 || Y2 < 0 || Y2 > n - 1)
continue;
if (!vis[X2][Y2]) {
vis[X2][Y2] = 1;
q.push({X2, Y2});
}
}
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
D.Tandem Repeats? (dp)
题意:
给你一个由小写拉丁字母或问号组成的字符串
s
s
s ,串联重复是一个偶数长度的字符串,其前半部分等于后半部分。
现在要求用某个小写英文字母替换每个问号,使串联重复的最长子串的长度尽可能最大。
分析:
d p [ i ] [ j ] dp[i][j] dp[i][j]表示从 i i i开始的后缀和从 j j j开始的后缀的最大前缀和,当符合 s [ i ] = = ′ ? ′ ∣ ∣ s [ j ] = = ′ ? ′ ∣ ∣ s [ i ] = = s [ j ] s[i] == '?' || s[j] == '?' || s[i] == s[j] s[i]==′?′∣∣s[j]==′?′∣∣s[i]==s[j]的时候,有转移方程 d p [ i ] [ j ] = d p [ i + 1 ] [ j + 1 ] + 1 ; dp[i][j] = dp[i + 1][j + 1] + 1; dp[i][j]=dp[i+1][j+1]+1;。再遍历一遍 d p dp dp数组,如果满足 d p [ i ] [ j ] ≥ j − i dp[i][j] \ge j-i dp[i][j]≥j−i,说明以 i i i 开头往后的 2 × ( j − i ) 2 \times (j - i) 2×(j−i)子串符合题意。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
int n = s.size();
s = " " + s;
vector<vector<int>> dp(n + 2, vector<int>(n + 2));
for (int i = n; i >= 1; i--) {
for (int j = n; j >= 1; j--) {
if (s[i] == '?' || s[j] == '?' || s[i] == s[j]) {
dp[i][j] = dp[i + 1][j + 1] + 1;
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (dp[i][j] >= j - i) {
ans = max(ans, (j - i) * 2);
}
}
}
cout << ans << endl;
}
return 0;
}
E.Clique Partition (图论)
题意:
给你两个整数 n n n 和 k k k 。在 n n n 个顶点上有一个图,编号从 1 1 1 到 n n n ,最初没有边。
现在要为每个顶点分配一个整数;让
a
i
a_i
ai 成为顶点
i
i
i 上的整数。所有的
a
i
a_i
ai 都应该是从
1
1
1 到
n
n
n 的不同整数。
分配整数后,对于每一对顶点
(
i
,
j
)
(i, j)
(i,j) ,如果有
∣
i
−
j
∣
+
∣
a
i
−
a
j
∣
≤
k
|i - j| + |a_i - a_j| \le k
∣i−j∣+∣ai−aj∣≤k 则在它们之间添加一条边。
现在的目标是创建一个可以分割成尽可能少的(对于给定的
n
n
n 和
k
k
k 值)小群的图。图中的每个顶点都应属于一个小群。一个小群是一个顶点集合,其中的每一对顶点都有一条边相连。
分析:
通过观察发现,我们需要选择一个连续的区间作为一个小群,因为连续的区间 ∣ i − j ∣ \vert i-j \vert ∣i−j∣是最小的。通过打表发现对于长度为 x x x的区间, ∣ i − j ∣ + ∣ a i − a j ∣ \vert i-j \vert + \vert a_i-a_j \vert ∣i−j∣+∣ai−aj∣同样是 x x x。接下来就可以按顺序排列,然后把一半的前缀放到后面去来进行构造。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
vector<int> ans;
vector<int> color;
int tmp = min(k, n);
int cnt = 0;
for (int i = 1; i <= n; i += tmp) {
int l = i, r = min(n, i + tmp - 1);
++cnt;
vector<int> vis;
for (int j = l; j <= r; j++) {
ans.push_back(cnt);
vis.push_back(j);
}
rotate(vis.begin(), vis.begin() + vis.size() / 2, vis.end());
color.insert(color.end(), vis.begin(), vis.end());
}
for (auto x: color)
cout << x << " ";
cout << endl;
cout << cnt << endl;
for (auto x: ans)
cout << x << " ";
cout << endl;
}
return 0;
}
F.Rare Coins (数学)
题意:
有
n
n
n 个袋子,编号从
1
1
1 到
n
n
n ,
i
i
i 个袋子里有
a
i
a_i
ai 枚金币和
b
i
b_i
bi 枚银币。
一枚金币的价值是
1
1
1 。一枚银币的价值是
0
0
0 或
1
1
1 ,由每枚银币独立决定(
0
0
0 的概率是
1
2
\frac{1}{2}
21 ,
1
1
1 的概率是
1
2
\frac{1}{2}
21 )。
回答
q
q
q 个独立的问题。每个问题如下:
- l l l r r r - 计算 l l l 至 r r r 袋中硬币的总价值严格大于所有其他袋中硬币总价值的概率。
分析:
计算概率实际上是计算有多少种银币的方案数使得自己更大。假设自己有 x x x枚银币变成 1 1 1,其他有 y y y枚变成 1 1 1。得到下列式子:自己的 a a a金币+ x > x > x>其他袋中的 a a a金币+ y y y。即设定一个约束 x − y > z x - y > z x−y>z,满足这个约束下的方案数为 ( b 1 x ) \tbinom{b_1}{x} (xb1) ( b 2 y ) \tbinom{b_2}{y} (yb2)
b 1 , b 2 b_1 , b_2 b1,b2分别表示自己有的银币和其他袋子里的银币。上述式子在形式上和范德蒙德卷积类似,只需要将 x − y x - y x−y改成 x + y x + y x+y即可。我们修改假设为,有 y y y枚变成 0 0 0,约束变为 a 1 + x > a 2 + b 1 − y a_1 + x > a_2 + b_1 - y a1+x>a2+b1−y, a 1 , a 2 a_1 , a_2 a1,a2和 b 1 , b 2 b_1, b_2 b1,b2定义类似。再由由范德蒙德卷积可知总方案数为 ( n z + 1 ) + … ( n n ) \tbinom{n}{z+1}+ \dots \tbinom{n}{n} (z+1n)+…(nn)。 n n n表示银币的总数。预处理后缀和之后即可进行计算。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 998244353;
const int N = 1e6 + 5;
LL binpow(LL a, LL n) {
LL ans = 1;
while (n) {
if (n & 1)
ans = (ans * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return ans;
}
LL inv(LL x) { return binpow(x, mod - 2); }
int a[N], b[N];
int prea[N], preb[N];
LL com[N], suf[N], fact[N], invfact[N];
int main() {
ios::sync_with_stdio(false);
int n, q, l, r;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
prea[i] = prea[i - 1] + a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
preb[i] = preb[i - 1] + b[i];
}
com[0] = 1;
fact[0] = 1;
for (int i = 1; i <= preb[n]; i++)
fact[i] = (fact[i - 1] * i) % mod;
invfact[preb[n]] = inv(fact[preb[n]]);
for (int i = preb[n] - 1; i >= 0; i--)
invfact[i] = (invfact[i + 1] * (LL) (i + 1)) % mod;
for (int i = 1; i <= preb[n]; i++)
com[i] = fact[preb[n]] * invfact[preb[n] - i] % mod * invfact[i] % mod;
suf[preb[n]] = com[preb[n]];
for (int i = preb[n] - 1; i >= 0; i--)
suf[i] = (suf[i + 1] + com[i]) % mod;
LL invs = inv(binpow(2, preb[n]));
for (int i = 1; i <= q; i++) {
cin >> l >> r;
int ina = prea[r] - prea[l - 1], inb = preb[r] - preb[l - 1];
int outa = prea[n] - ina, outb = preb[n] - inb;
int condition = max(outa + outb - ina, -1);
LL ans = (condition >= preb[n] ? 0 : suf[condition + 1]);
ans = (ans * invs) % mod;
cout << ans << " ";
}
cout << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。