Sorry for the long wait. All problems were written, prepared, and editorialized by Yam, hyforces, HaccerKat, alexlikemath007, culver0412, jay_jayjay, n685, Mustela_Erminea, Jasonwei08, eysbutno, training4usaco, nyctivoe, and gg_gong. The problems (with problem credits) can be found in the Novice Gym and Advanced Gym. Also thanks to our testers for providing valuable feedback and to our logistics and web team for making TeamsCode possible!
Novice A/Advanced A: Lily Pads
This is the first problem — don't overthink it!
Try a few examples on paper — see if you notice any patterns.
Try to focus on just one row of lilypads, and it's contribution to the total score.
Notice that for each row, both lily pads will get jumped on. If you focus on a row, the first frog will jump onto the currently afloat lily pad, and when it leaves, the other lily pad goes afloat, causing the second frog to jump on it.
Therefore, for each row, one of the frogs will jump on the right lily pad, and the other will jump on the left. Each row contributes 1 point to the answer, so the answer is simply the number of rows, which is $$$N$$$.
n = int(input())
print(n)
Novice B: Sacrifice the Rook
This might be annoying to code, but just do it.
Watch out for edge cases, and read the sample explainations.
You can show that only considering rook moves is sufficient.
You simply need to check every possible rook and king move, and then check if it is possible for the opponent's king to capture your rook. Make sure that your rook doesn't jump over your king, and that your king doesn't "defend" your rook after you make your move. (See sample test case 5).
A shortcut you can take is that you don't have to check king moves. If your rook is able to be captured if the king moves, that means it must be diagonally adjacent to the opponent's king. (Otherwise, their king is in check, and the position is not valid.) Thus, there are two possible rook moves to become adjacent to the opponent's king, and your king can't defend both of these cells to stop you from sacrificing your rook.
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define fi first
#define se second
bool isAdjacent(pii p1, pii p2) { return (abs(p1.fi - p2.fi) <= 1 && abs(p1.se - p2.se) <= 1); }
bool pathClear(pii start, pii end, pii wk, pii bk) {
if (start.se == end.se) {
int col1 = min(start.fi, end.fi), col2 = max(start.fi, end.fi);
for (int col = col1 + 1; col < col2; col++) {
if ((wk.fi == col && wk.se == start.se) || (bk.fi == col && bk.se == start.se))
return 0;
}
} else if (start.fi == end.fi) {
int row1 = min(start.se, end.se), row2 = max(start.se, end.se);
for (int row = row1 + 1; row < row2; row++) {
if ((wk.fi == start.fi && wk.se == row) || (bk.fi == start.fi && bk.se == row))
return 0;
}
}
return 1;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
int t;
cin >> t;
for(int tc = 0; tc < t; tc++){
string wks, wrs, bks;
cin >> wks >> wrs >> bks;
auto toPos = [&](string s) -> pii { return {s[0] - 'a' + 1, s[1] - '0'}; };
pii wk = toPos(wks), wr = toPos(wrs), bk = toPos(bks);
bool flg = 0;
for (int dc = -1; dc <= 1; dc++) {
for (int dr = -1; dr <= 1; dr++) {
if (dc == 0 && dr == 0)
continue;
pii cur = {bk.fi + dc, bk.se + dr};
if (cur.fi < 1 || cur.fi > 8 || cur.se < 1 || cur.se > 8)
continue;
if (cur.fi != wr.fi && cur.se != wr.se)
continue;
if ((cur.fi == wk.fi && cur.se == wk.se) ||
(cur.fi == bk.fi && cur.se == bk.se))
continue;
if (!pathClear(wr, cur, wk, bk))
continue;
if (isAdjacent(cur, wk))
continue;
flg = true;
}
}
cout << (flg ? "YES" : "NO") << "\n";
}
return 0;
}
Novice C: Fill the world with Argon
Notice how in the sample, argon is poured on the cell with the largest height.
Argon cannot move up. Therefore, if a cell is surrounded by cells with lower heights, you must pour argon on that cell. Call such cells peaks.
Is pouring argon on all peaks sufficient?
Call cells peaks if it has no higher adjacent cells. Note that you must pour argon on peaks since argon from adjacent cells cannot flow to a peak. It also turns out pouring argon on all the peaks is sufficient.
Proof: Assume for the sake of contradiction that argon is poured on all peaks, and the world is not filled. Consider the cell $$$X$$$ with the largest height that is not filled with argon. Note that $$$X$$$ cannot be a peak, as otherwise argon would have been poured on it. Thus, there must be a cell $$$Y$$$ higher than it that is adjacent to it. And, $$$Y$$$ cannot be filled with argon, as otherwise, argon would have flowed from $$$Y$$$ to $$$X$$$, and filled $$$X$$$ with argon. However, this creates a contradiction, as $$$Y$$$ has a higher height and is not filled with argon, contradicting that $$$X$$$ has the largest height and is not filled with argon.
Therefore you can simply loop through all cells, and check for cells that satisfy this, in $$$O(NM)$$$.
/**
* @author n685
* @date 2025-03-22 19:02:42
*/
#include <bits/stdc++.h>
#ifdef LOCAL
#include "dd/debug.h"
#else
#define dbg(...) 42
#define dbg_proj(...) 420
#define dbg_rproj(...) 420420
void nline() {}
void bar() {}
void start_clock() {}
void end_clock() {}
#endif
namespace rs = std::ranges;
namespace rv = std::views;
using u32 = unsigned int;
using i64 = long long;
using u64 = unsigned long long;
const std::array<int, 4> dx{-1, 0, 1, 0}, dy{0, 1, 0, -1};
int main() {
#ifndef LOCAL
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#endif
int n, m;
std::cin >> n >> m;
std::vector a(n, std::vector<int>(m));
for (std::vector<int>& arr : a) {
for (int& val : arr) {
std::cin >> val;
}
}
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
bool good = true;
for (int d = 0; d < 4; ++d) {
int x = i + dx[d];
int y = j + dy[d];
if (x < 0 || x >= n || y < 0 || y >= m) {
continue;
}
if (a[i][j] < a[x][y]) {
good = false;
break;
}
}
if (good) {
++ans;
}
}
}
std::cout << ans << '\n';
}
Read the main solution first.
We iterate over the cells in decreasing order of height. For each cell without argon, we simulate the pouring of argon using a flood fill algorithm like dfs or bfs and add $$$1$$$ to the answer. This process takes $$$\mathcal{O}(nm \log (nm))$$$ time due to sorting the cells.
This process works because it turns out the only cells in this process that we pour argon on are peaks. For peaks, argon cannot flow from another cell to it, so we pour argon onto peaks. For non-peaks, based on the proof of the main solution, if all cells higher than it contain argon, then the non-peak has to contain argon as well. Since in this process, when processing a cell, we make sure all higher cells contain argon, we do not pour argon on non-peaks.
/**
* @author n685
* @date 2025-03-22 19:10:16
*/
#include <bits/stdc++.h>
#ifdef LOCAL
#include "dd/debug.h"
#else
#define dbg(...) 42
#define dbg_proj(...) 420
#define dbg_rproj(...) 420420
void nline() {}
void bar() {}
void start_clock() {}
void end_clock() {}
#endif
using u32 = unsigned int;
using i64 = long long;
using u64 = unsigned long long;
const std::array<int, 4> dx{-1, 0, 1, 0}, dy{0, 1, 0, -1};
int main() {
#ifndef LOCAL
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#endif
int n, m;
std::cin >> n >> m;
std::vector a(n, std::vector<int>(m));
for (std::vector<int>& arr : a) {
for (int& val : arr) {
std::cin >> val;
}
}
std::vector<std::pair<int, int>> coord;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
coord.emplace_back(i, j);
}
}
std::sort(
coord.begin(),
coord.end(),
[&](const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
return a[lhs.first][lhs.second] > a[rhs.first][rhs.second];
}
);
std::vector fl(n, std::vector<bool>(m));
auto dfs = [&](auto&& self, int i, int j) -> void {
for (int d = 0; d < 4; ++d) {
int x = i + dx[d];
int y = j + dy[d];
if (x < 0 || x >= n || y < 0 || y >= m || fl[x][y] || a[i][j] < a[x][y]) {
continue;
}
fl[x][y] = true;
self(self, x, y);
}
};
int ans = 0;
for (auto [i, j] : coord) {
if (!fl[i][j]) {
++ans;
fl[i][j] = true;
dfs(dfs, i, j);
}
}
std::cout << ans << '\n';
}
Novice D: Please solve this in O(N^3)
There are two ways to solve this problem — the normal way, and the cool $$$O(N^3)$$$ way.
Normal Way:
Consider one color at a time.
You can use algorithms like Depth First Search, Breadth First Search, or Disjoint Set Union to find connected components in a graph, and thus determine whether two nodes are connected by some path.
Consider a "subgraph" that includes only nodes of a specific color. If there is a path from $$$i$$$ to $$$j$$$ in the subgraph, that means there is a valid path from $$$i$$$ to $$$j$$$ in the graph with all nodes sharing that color in common. Therefore, we can just find the connectivity of nodes in the subgraphs for each color using DFS, BFS or DSU, and if two nodes are connected in a subgraph, we can mark them as connected.
This has a time complexity of $$$O(30 N^2)$$$. This is faster than the $$$O(N^3)$$$ solution, but not as cool.
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define fi first
#define se second
const int N = 755;
struct DSU {
vector<int> fa;
DSU(int n) : fa(n) { iota(fa.begin(), fa.end(), 0); }
int find(int a) { return fa[a] == a ? a : fa[a] = find(fa[a]); }
void merge(int a, int b) {
a = find(a), b = find(b);
if (a != b) fa[b] = a;
}
};
int n, m, cm[N];
vector<pii> G;
bitset<N> vis[N];
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> cm[i];
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v, u--, v--;
G.push_back({u, v});
}
for (int i = 0; i < n; i++)
vis[i].reset(), vis[i].set(i, 1);
for (int c = 0; c < 30; c++) {
DSU dsu(N);
for (auto &[u, v] : G) {
if ((cm[u] & (1 << c)) && (cm[v] & (1 << c)))
dsu.merge(u, v);
}
unordered_map<int, vector<int>> mp;
for (int i = 0; i < n; i++) {
if (cm[i] & (1 << c)) {
int rt = dsu.find(i);
mp[rt].push_back(i);
}
}
for (auto &p : mp) {
vector<int> &lc = p.se;
for (auto u : lc)
for (auto v : lc)
vis[u].set(v, 1);
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
cout << (vis[i].test(j) ? '1' : '0');
cout << "\n";
}
return 0;
}
Cool way:
Is there a famous graph algorithm that runs in $$$O(N^3)$$$?
The famous graph algorithm is the Floyd-Warshall algorithm. Could you adapt it to this problem using bitwise operations?
Note that Floyd-Warshall can be modified to check for connectivity in a graph.
Normally, when running Floyd-Warshall, the statement $$$\text{dist}[j][k] = \text{min}(\text{dist}[j][k], \text{dist}[j][i] + \text{dist}[i][k])$$$ is used.
However, Floyd-Warsahll can also be used to check for connectivity. Define $$$\text{connected}[i][j] = \text{true}$$$ if $$$i$$$ and $$$j$$$ are connected, and $$$\text{false}$$$ otherwise. Then, we follow the same steps as the normal solution, initialize the adjacency matrix for each subgraph, and run Floyd-Warshall. Our update is $$$\text{connected}[j][k] = \text{connected}[j][k] \ \text{||} \ (\text{connected}[j][i] \ \text{&&} \ \text{connected}[i][k])$$$. That is, if there is a path between $$$j$$$ to $$$i$$$, and there is a path between $$$i$$$ to $$$k$$$, we should update the array to show that we found a path between $$$j$$$ to $$$k$$$.
This has a time complexity of $$$O(30N^3)$$$.
Notice that instead of running $$$30$$$ Floyd Warshalls, we can simply run all of them at once by using bitwise operations. Define $$$\text{bitmaskConnectivity}[i][j]$$$ to be a bitmask, where the $$$k$$$th bit is $$$1$$$ if there is a path between $$$i$$$ and $$$j$$$ such that all nodes on the path contain the color $$$k$$$.
We initialize $$$\text{bitmaskConnectivity}[i][i] = a[i]$$$, and if there is an edge between $$$i$$$ and $$$j$$$, $$$\text{bitmaskConnectivity}[i][j] = a[i] \ \text{&} \ a[j]$$$, and our update is $$$\text{bitmaskConnectivity}[j][k] = \text{bitmaskConnectivity}[j][k] \ \text{|} \ (\text{bitmaskConnectivity}[j][i] \ \text{&} \ \text{bitmaskConnectivity}[i][k])$$$
This has a time complexity of $$$O(N^3)$$$.
Although this solution might be very complicated, the code is short and simple!
#include <iostream>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int n, m;
cin >> n >> m;
int a [n];
for(int i = 0; i < n; i++){
cin >> a[i];
}
int edg [n][n];
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
edg[i][j] = 0;
}
}
for(int i = 0; i < n; i++){
edg[i][i] = a[i];
}
for(int i = 0; i < m; i++){
int x, y;
cin >> x >> y;
x--;
y--;
edg[x][y] = edg[y][x] = (a[y] & a[x]);
}
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
for(int k = 0; k < n; k++){
edg[j][k] |= edg[j][i] & edg[i][k];
}
}
}
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
cout << (edg[i][j] != 0);
}
cout << '\n';
}
return 0;
}




