A.Welcome to AtCoder Land
题意
给出两个字符串 S , T S, T S,T,请你判断是否满足:
-
字符串 S S S为
AtCoder
-
字符串 T T T为
Land
分析
输入后判断即可
代码
#include<bits/stdc++.h>
using namespace std;
void solve() {
string s, t;
cin >> s >> t;
if (s == "AtCoder" && t == "Land") {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
int main() {
solve();
return 0;
}
B.Ticket Counter(思维)
题意
有 N N N个人会进入会场买票,每个人买票均需要花费 A A A分钟,其中第 i i i个人会在 T i T_i Ti时间到达,如果到达后前面的人还没买完票,就需要等待前面的人完成后继续,否则,可以直接开始买票。
问:每个人买票结束的时间是多少。
分析
由于保证了到达时间是有序的,那么可以依次遍历每个人,第 i i i个人的开始买票时间为第 i − 1 i - 1 i−1个人的结束时间和第 i i i个人的到达时间中的较大值,那么结束时间即为开始时间加上花费的时间。
输出每个人买票结束的时间即可。
代码
#include<bits/stdc++.h>
using namespace std;
int t[105];
void solve() {
int n, a;
cin >> n >> a;
int now = 0;
for (int i = 1; i <= n; i++) {
cin >> t[i];
now = max(now, t[i]);
now += a;
cout << now << endl;
}
}
int main() {
solve();
return 0;
}
C. Popcorn(二进制枚举)
题意
有 N N N种爆米花,爆米花总共有 M M M种味道,使用一个包含 M M M个字符的字符串 S i S_i Si表示第 i i i种爆米花拥有的味道,即使用 S i , j = ′ o ′ S_{i, j} = 'o' Si,j=′o′表示第 i i i种爆米花拥有 j j j味道,用 S i , j = ′ x ′ S_{i, j} = 'x' Si,j=′x′表示第 i i i种爆米花没有 j j j味道。
问:最少买几种爆米花,才能包含所有味道。
分析
观察数据可以发现, N , M ≤ 10 N, M \le 10 N,M≤10,那么数据量是非常小的,可以直接使用二进制枚举去枚举所有选择方案,记录包含所有味道中,选择爆米花种类最小的一个即为最后的答案。
代码
#include<bits/stdc++.h>
using namespace std;
string s[105];
int vis[15];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> s[i];
}
int ans = n;
for (int i = (1 << n) - 1; i >= 0; i--) {
memset(vis, 0, sizeof (vis));
int cnt = 0, sum = 0;
for (int j = 0; j < n; j++) {
if (i & (1 << j)) {
cnt++;
for (int k = 0; k < m; k++) {
if (s[j][k] == 'o') {
vis[k]++;
if (vis[k] == 1) sum++;
}
}
}
}
if (sum == m) {
ans = min(ans, cnt);
}
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
D.Souvenirs(贪心)
题意
有 N N N个糖果盒,第 i i i个盒子价格为 A i A_i Ai,且包含 A i A_i Ai颗糖果。
你需要买其中 M M M盒糖果,并把这些盒子送给你的朋友,送给第 j j j个朋友的盒子需要至少包含 B j B_j Bj颗糖果。
如果能买到满足要求的
M
M
M盒糖果,输出最小的花费,如果不能,输出-1
。
分析
首先一定要优先考虑给糖果需求大的选取盒子,那么该怎么选择最优呢,必然会去选择所有满足要求的盒子中 ( B [ j ] ≤ A [ i ] ) (B[j] \le A[i]) (B[j]≤A[i]),价格最低的一个,对于这些盒子,可以使用优先队列维护所有价值大于等于 B [ j ] B[j] B[j]的糖果盒子,每次从优先队列取出最小的元素加到答案中,如果取不出来,则说明无解。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<int, vector<int>, greater<int> >Q;
const int N = 2e5 + 5e2;
int n, m, a[N], b[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int j = 1; j <= m; j++) cin >> b[j];
sort(a + 1, a + n + 1);
sort(b + 1, b + m + 1);
ll ans = 0;
for (int i = m, j = n; i >= 1; i--) {
while (j >= 1 && a[j] >= b[i]) {
Q.push(a[j--]);
}
if (Q.empty()) {
cout << -1 << endl;
return;
}
ans += Q.top();
Q.pop();
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
E.Alphabet Tiles(DP)
题意
给出26个字母中每个字母的数量,即使用 a i a_i ai表示第 i i i个字母的个数。
定义函数 f ( i ) f(i) f(i)表示使用给出的字母组成长度为 i i i的字符串的个数。
给出一个正整数 k k k,请你求出:
- ∑ i = 1 k f ( i ) \sum\limits_{i = 1}^{k}f(i) i=1∑kf(i)
hint:结果较大,需对 998244353 998244353 998244353取模
分析
定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示选择前 i i i个字母,组成 j j j长度的字符串的方案数。
状态转移:可以在前面所有字母已经放置了 j j j个字母的方案中,再放置 l l l个当前字母,此时的方案数可以视为总共 j + l j + l j+l个空位,要给前面的 j j j个字母放入这些空位中,此时摆放的方案数为 C j + l j C_{j + l}^{j} Cj+lj,产生的总方案数即为 d p [ i − 1 ] [ j ] × C j + l j dp[i - 1][j] \times C_{j + l}^{j} dp[i−1][j]×Cj+lj
完成状态转移后,答案即为 ∑ i = 1 k d p [ 26 ] [ i ] \sum\limits_{i = 1}^{k}dp[26][i] i=1∑kdp[26][i]
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5e2, mod = 998244353;
ll C[N][N], k, a[N], dp[N][N];
void init() {
C[0][0] = 1;
for (int i = 1; i <= 1000; i++) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
}
void solve() {
cin >> k;
for (int i = 1; i <= 26; i++) cin >> a[i];
dp[0][0] = 1;
for (int i = 1; i <= 26; i++) {
for (int j = 0; j <= k; j++) {
for (int l = 0; l <= a[i]; l++) {
if (j + l > k) break;
dp[i][j + l] = (dp[i][j + l] + dp[i - 1][j] * C[j + l][j] % mod) % mod;
}
}
}
ll ans = 0;
for (int i = 1; i <= k; i++) {
ans = (ans + dp[26][i]) % mod;
}
cout << ans << endl;
}
int main() {
init();
solve();
return 0;
}
F.Easiest Maze(模拟、构造)
题意:
斯努克计划在AtCoder乐园中建造一个迷宫作为新景点。迷宫是一个有 N N N行和 M M M列的网格,右上角单元格的顶边是入口,右下角单元格的底边是出口。他将通过在相邻单元格之间适当放置墙壁来创建迷宫。
他喜欢简单的迷宫,因此他希望从入口到出口的路径正好经过 K K K个单元格,且没有任何分支。请判断是否可能创建这样一个迷宫,如果可能,请建造一个。
例如,在下图中, N = 3 N=3 N=3和 M = 3 M=3 M=3,并在实线处设置墙壁(除入口和出口外,墙壁总是设置在外围)。在这种情况下,从入口到出口的路径正好经过 7 7 7个单元格,没有任何分支。
下面是一个正式的说明。
有一个网格,网格中有 N N N行和 M M M列。让 ( i , j ) (i,j) (i,j)表示从上往下第 i i i行,从左往上第 j j j列的单元格。对于每一对边相邻的单元格,你可以决定是否在它们之间放置一堵墙。确定是否有可能放置墙来满足以下条件,如果有可能,请构建一个这样的放置方式。
考虑一个有 N × M N\times M N×M个顶点的无向图 G G G。 G G G的每个顶点都由一对整数 ( i , j ) ( 1 ≤ i ≤ N , 1 ≤ j ≤ M ) (i,j)(1\leq i\leq N,1\leq j\leq M) (i,j)(1≤i≤N,1≤j≤M)唯一标注。两个不同的顶点 ( i 1 , j 1 ) (i_1,j_1) (i1,j1)和 ( i 2 , j 2 ) (i_2,j_2) (i2,j2)由一条边连接,当且仅当 ∣ i 1 − i 2 ∣ + ∣ j 1 − j 2 ∣ = 1 |i_1-i_2|+|j_1-j_2|=1 ∣i1−i2∣+∣j1−j2∣=1和网格上对应的单元格 ( i 1 , j 1 ) (i_1,j_1) (i1,j1)和 ( i 2 , j 2 ) (i_2,j_2) (i2,j2)之间没有墙。
条件:存在一条顶点为 K K K的简单路径连接两个顶点 ( 1 , M ) (1,M) (1,M)和 ( N , M ) (N,M) (N,M),且包含顶点 ( 1 , M ) (1,M) (1,M)和 ( N , M ) (N,M) (N,M)的连通部分仅由这条路径组成。
分析:
我们先将路径设为最短路径,再对其进行扩充。可以发现:如果进行扩充,这样增长的格子数一定是偶数。因为如果你要扩充,就会出去,出去了然后需要回来,出去和回来的路径长度一定是一样的,所以扩张只会增加偶数个格子。
先考虑无解的情况:
- 要求格子数大于能走过的总格子数。
- 要求格子数小于最短路径格子数。
- 要求格子数的奇偶性与能走过的总格子数的奇偶性不同。
此处考虑“能走过的总格子数”因为不一定能够走过全部的格子,例如:
- N N N为偶数,如 N = 4 , M = 4 N=4,M=4 N=4,M=4,那么可以走遍所有格子;
- N N N为奇数,需要分类讨论:
- M M M为奇数,那么可以走遍所有格子;
- M M M为偶数,那么不可以走遍所有格子,会空一个格子;
然后考虑如何进行扩充,先计算出路径需要扩张几次:即求格子数减去最短路径格子数的差除以二。
接下来进行扩张,先横着扩张,一边扩张还要一边判断需要的扩张次数到了没有。横着扩充完了,再竖着扩张,虽然竖着扩张是
N
N
N为奇数独有的,但是因为剩下的都是有解的,所以
N
N
N为偶数的情况需要的扩张次数应该在横着扩张那里就满足了,最后将路径转换为迷宫问题来输出即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
const int MOD = 998244353;
int a[N][N], num;
int dx[10] = {0, -1, 0, 1, 0}, dy[10] = {0, 0, 1, 0, -1};
char ans[202][202];
int main() {
int n, m, k;
cin >> n >> m >> k;
num = (k - n) / 2;
if (k > n * m || k < n || (n % 2 == 0 && k % 2 == 1) || (n % 2 == 1 && m % 2 == 0 && (n * m - 1) % 2 != k % 2) ||
(n % 2 == 1 && m % 2 == 1 && (n * m) % 2 != k % 2)) {
cout << "No" << endl;
return 0;
}
cout << "Yes" << endl;
for (int i = 1; i <= n; i++)
a[i][m] = 3;
for (int i = 1; i < n && num > 0; i += 2)
for (int j = m - 1; j >= 1 && num > 0; j--, num--) {
a[i + 1][j] = 2;
a[i][j] = 3;
a[i][j + 1] = 4;
}
if (num != 0) {
if (m % 2 == 0) {
for (int i = 1; i < m - 1 && num > 0; i += 2, num--) {
a[n - 1][i] = 3;
a[n][i] = 2;
a[n][i + 1] = 1;
}
} else {
for (int i = 1; i < m && num > 0; i += 2, num--) {
a[n - 1][i] = 3;
a[n][i] = 2;
a[n][i + 1] = 1;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ans[i * 2 + dx[a[i][j]]][j * 2 + dy[a[i][j]]] = '.';
}
}
for (int i = 1; i <= n * 2; i += 2) {
for (int j = 1; j <= m * 2; j += 2) {
ans[i][j] = '+';
ans[i + 1][j + 1] = 'o';
if (ans[i + 1][j] != '.')
ans[i + 1][j] = '|';
if (ans[i][j + 1] != '.')
ans[i][j + 1] = '-';
}
}
for (int i = 1; i <= n * 2 + 1; i++) {
ans[i][m * 2 + 1] = '+';
ans[i][1] = '+';
}
for (int i = 1; i <= m * 2 + 1; i++) {
ans[n * 2 + 1][i] = '+';
ans[1][i] = '+';
}
ans[1][m * 2] = 'S';
ans[n * 2 + 1][m * 2] = 'G';
for (int i = 1; i <= n * 2 + 1; i++) {
for (int j = 1; j <= m * 2 + 1; j++)
cout << ans[i][j];
cout << endl;
}
return 0;
}
G.AtCoder Tour(动态规划)
题意:
AtCoder Land 是一个有 H H H行和 W W W列的网格。让 ( i , j ) (i,j) (i,j)表示第 i i i行和第 j j j列上的单元格。
高桥从 ( S i , S j ) (S_i,S_j) (Si,Sj)单元格开始,重复以下操作 K K K次:
- 他要么停留在当前单元格,要么移动到相邻单元格。在这个操作之后,如果他在 ( i , j ) (i,j) (i,j)单元格中,他将获得 A i , j A_{i,j} Ai,j的趣味值。
请找出他能获得的最大总乐趣值。
在这里,当且仅当 ∣ x − x ′ ∣ + ∣ y − y ′ ∣ = 1 |x-x'|+|y-y'|=1 ∣x−x′∣+∣y−y′∣=1时, ( x ′ , y ′ ) (x',y') (x′,y′)单元格被视为与 ( x , y ) (x,y) (x,y)单元格相邻。
分析:
在最优解中,显然会且只会在同一个方格(路径上权值最大的方格)停留,并且一直停留直到无法再进行操作,正确性很好说明:若停留在其他方格,不如在最后用这些操作数停留在路径上权值最大的方格贡献更大;若路径上权值最大的方格不是路径的终点,那么遍历剩下的点的操作数还不如停留在这个方格。
设 d p k , i , j dp_k,i,j dpk,i,j表示经过 k k k个操作并到达 ( i , j ) (i,j) (i,j)能得到的最大贡献,其中 k ≤ m i n ( h × w , v ) k≤min(h×w,v) k≤min(h×w,v)( v v v就是题面中的 K K K),跑完 d p dp dp后再处理停留的问题即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 55;
const int MOD = 998244353;
int a[N][N];
LL dp[2510][N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
int check(int ux, int uy, int vx, int vy) {
return abs(ux - vx) + abs(uy - vy);
}
int main() {
int h, w, v;
cin >> h >> w >> v;
int stx, sty;
cin >> stx >> sty;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++)
cin >> a[i][j];
}
LL ans = 0;
for (int k = 1; k <= min(v, h * w); k++) {
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
if (check(i, j, stx, sty) <= 1) {
LL temp = dp[k - 1][stx][sty];
dp[k][i][j] = max(dp[k][i][j], temp + a[i][j]);
}
if (dp[k - 1][i][j]) {
LL temp = dp[k - 1][i][j];
dp[k][i][j] = max(dp[k][i][j], temp + a[i][j]);
}
for (int c = 0; c < 4; c++) {
int vx = i + dx[c], vy = j + dy[c];
if (dp[k - 1][vx][vy]) {
LL temp = dp[k - 1][vx][vy];
dp[k][i][j] = max(dp[k][i][j], temp + a[i][j]);
}
}
}
}
}
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
int dis = check(i, j, stx, sty);
if (dis <= v) {
for (int k = 1; k <= min(v, h * w); k++) {
if (dp[k][i][j]) {
ans = max(ans, dp[k][i][j] + 1ll * (v - k) * a[i][j]);
}
}
}
}
}
cout << ans << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。