AtCoder Solutions 2: DP, Probability, Graphs, and Data Structures
April~July 2024, primarily based on original solution records, including ABC 348 E/F, 350 D/E, 351 D/E/F, 357 D/E/F, 358 C/E/G, 360 E.
Scope of Coverage#
ABC 348 EABC 348 FABC 350 DABC 350 EABC 351 DABC 351 EABC 351 FABC 357 DABC 357 EABC 357 FABC 358 CABC 358 EABC 358 GABC 360 E
ABC 348 E - Minimize Sum of Distances#
You are given a tree with vertices. The vertices are numbered to , and the -th edge connects vertices and .
We are also given a sequence of positive integers. Let be the number of edges between vertices and , and for , define . Find .
Solution#
Rerooting DP / Tree DP / Rerooting / Tree Centroid
Original: Problem - F - Codeforces ↗ However, that problem asks for the maximum value. The initial processing is the same, but the second DFS differs:
void go(int v, int p = -1) {
ans = max(ans, res);
for (auto to : g[v]) {
if (to == p) {
continue;
}
res -= sum[to];
sum[v] -= sum[to];
res += sum[v];
sum[to] += sum[v];
go(to, v);
sum[to] -= sum[v];
res -= sum[v];
sum[v] += sum[to];
res += sum[to];
}
}cppA similar approach for this problem:
ll ans = INF;
void dfs(int u, int fa) {
ans = min(ans, res);
for (auto v : G[u]) {
if (v == fa) {
continue;
}
res -= sum[v];
res += sum[1] - sum[v];
dfs(v, u);
res += sum[v];
res -= sum[1] - sum[v];
}
}cppFirst, scan the tree with as the root node. represents the sum of weights of node and its subtree. The weight of the root node must be the sum of all weights.
Then find the node whose weight accounts for more than half of the total weight. If such a node exists, taking as the new root, since its own weight is not counted in the distances from it, this saves the maximum amount of weight, thus minimizing the answer.
If it equals half the total weight, whether to change the root or not doesn’t matter.
If the root node remains unchanged, then it is already the minimum.
Generally speaking, what is described above can be called the centroid of a tree. It can be proven that every tree has a centroid, and there are at most two centroids.
When the centroid of a tree is taken as the root, the size of all subtrees does not exceed half the size of the entire tree. Here, ‘size’ carries weight, meaning weighted size.
Tree Centroid - OI Wiki ↗ About the centroid (center of mass) of a tree, dedicated to solving graph theory and its optimization problems.
void solve() {
int n;cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 1;i < n;i++) {
int u, v;cin >> u >> v;
g[u].push_back(v);g[v].push_back(u);
}
vector<int>c(n + 1);
for (int i = 1;i <= n;i++)cin >> c[i];
vector<ll> sum(n + 1);
auto dfs1 = [&](auto self, int x, int p) -> void {
sum[x] = c[x];
for (auto y : g[x]) {
if (y == p)continue;
self(self, y, x);
sum[x] += sum[y];
}
};
dfs1(dfs1, 1, 0);
auto dfs2 = [&](auto self, int x, int p) -> int {
for (auto y : g[x]) {
if (y == p || 2 * sum[y] <= sum[1])continue;
return self(self, y, x);
}
return x;
};
int x = dfs2(dfs2, 1, 0);
dfs1(dfs1, x, 0);
ll ans = 0;
for (int i = 1;i <= n;i++) {
if (i != x)ans += sum[i];
}
cout << ans << '\n';
}cppAlternatively: This problem is a template for rerooting DP, but the approach above is not provided. Here is the code:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n; cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<i64> c(n + 1);
for (int i = 1; i <= n; ++i)
cin >> c[i];
vector<i64> dp(n + 1), res(n + 1);
auto pre_dfs = [&](auto &f, int fa, int u)->void {
dp[u] = c[u];
for (auto &v : g[u]) {
if (v == fa) continue;
f(f, u, v);
dp[u] += dp[v];
res[u] += res[v] + dp[v];
}
};
pre_dfs(pre_dfs, 1, 1);
auto dfs = [&](auto &f, int fa, int u)->void {
if (fa != u)
res[u] = res[fa] - (res[u] + dp[u]) + (dp[1] - dp[u]) + res[u];
// res[u] = res[fa] + dp[1] - 2 * dp[u]
for (auto &v : g[u]) {
if (v == fa) continue;
f(f, u, v);
}
};
dfs(dfs, 1, 1);
cout << *min_element(begin(res) + 1, end(res)) << '\n';
}cppABC 348 F - Oddly Similar#
There are sequences of length , denoted as . The -th sequence consists of integers .
Two sequences and of length are said to be similar if and only if the number of indices () where is odd.
Find the number of pairs of integers satisfying such that and are similar.
Solution#
bitset / Technique
#pragma GCC optimize("Ofast,unroll-loops")cppTurning on O3 allows a direct brute-force pass.
int ans = 0;
for (int i = 0;i < n;i++) {
for (int j = i + 1;j < n;j++) {
int cnt = 0;
for (int k = 0;k < m;k++) {
if (a[i][k] == a[j][k])cnt++;
}
if (cnt & 1)ans++;
}
}
cout << ans << '\n';cppBitset approach for a forced time complexity:
bitset<2010> f[2010][1000];
void solve() {
int n, m;cin >> n >> m;
vector<vector<int>>a(n, vector<int>(m));
for (int i = 0;i < n;i++) {
for (int j = 0;j < m;j++) {
cin >> a[i][j];
f[j][a[i][j]][i] = 1;
}
}
int ans = 0;
for (int i = 0;i < n;i++) {
bitset<2010> g;
for (int j = 0;j < m;j++) {
g ^= f[j][a[i][j]];
}
g[i] = 0;
ans += g.count();
}
cout << ans / 2 << '\n';
}cppABC 350 D - New Friends#
There is an SNS used by users, labeled from to .
In this SNS, two users can become friends with each other. Friendship is bidirectional; if user X is a friend of user Y, then user Y is always a friend of user X.
Currently, there are pairs of friends on the SNS, where the -th pair consists of users and .
Determine the maximum number of times the following operation can be performed:
- Operation: Choose three users X, Y, and Z such that X and Y are friends, Y and Z are friends, but X and Z are not friends. Make X and Z friends.
This problem can be seen at a glance: For each connected component: size*(size-1)/2 - number of edges in the component. Let Connected-Component , size: , number of edges:
i.e.:
#define int long long
void solve() {
int n, m;cin >> n >> m;vector<vector<int>>g(n + 1);
vector<int> vis(n + 1);
for (int i = 1;i <= m;i++) {
int a, b;cin >> a >> b;g[a].push_back(b);g[b].push_back(a);
}
int ans = 0, res = 0, cnt = 0;
auto dfs = [&](auto self, int v) ->void {
vis[v] = 1;res++;
cnt += g[v].size();
for (auto i : g[v]) {
if (vis[i])continue;
self(self, i);
}
};
for (int i = 1;i <= n;i++) {
if (!vis[i]) {
res = 0, cnt = 0;
dfs(dfs, i);
ans += res * (res - 1) / 2 - cnt / 2;
}
}
cout << ans << '\n';
}cppABC 350 E - Toward 0#
You are given an integer . You can perform the following two types of operations:
- Pay yen and replace with .
- Pay yen and roll a die that shows an integer between and with equal probability. Let be the result of the die roll, and replace with .
Here, denotes the greatest integer less than or equal to . For example, and .
Find the minimum expected cost required for to become when operations are chosen optimally. The result of each die roll is independent of other die rolls, and operations can be chosen after observing the results of previous operations.
Solution#
Expectation / Memoized Search
This problem can be solved using memoized recursion.
Problem 1
First, consider the following problem.
The setup is the same as the original problem. However, there is only one type of operation.
- Pay Y yen. Roll a die that shows an integer from 2 to 6 with equal probability. Replace N with .
Let the expected value be denoted as . Then,
Therefore, we can compute this via memoized recursion. (Regarding computational complexity, we will mention it later.)
Problem 2
Next, consider the following problem.
The setup is the same as the original problem. However, there is only one type of operation.
- Pay Y yen. Roll a die that shows an integer from 1 to 6 with equal probability. Replace N with .
Let the expected value be denoted as . Then,
The right-hand side also contains , so it seems impossible to compute recursively, but we can move the term to the left and multiply both sides by to obtain
Thus, we can compute this via memoized recursion. (Regarding computational complexity, we will mention it later.)
Original Problem
Consider the original problem. Let the expected value be denoted as . Since there are two types of operations, it is optimal to choose the one with the smaller expected value.
Therefore, we can compute this via memoized recursion.
To compute , we note that , so only appears for integers that can be written in the form .
Such are at most , so the overall computational complexity is .
Summary
Following the official explanation ↗, let the expected value we seek be . Also, call the two operations in the problem Operation A and Operation B.
If we model the original problem directly, we get the following equation:
The official explanation ↗ might be a bit vague, but because of the , we cannot simply use algebraic manipulation to remove from the right-hand side.
Therefore, we can replace the two operations in the problem with the following two operations to make discussion easier. Through this replacement, we can obtain the same equation as in the official explanation ↗.
- Pay yen. Replace with . (Unchanged)
- “Pay yen, and roll a uniformly distributed die until a number between 2 and 6 appears.” Use the resulting integer to replace with .
If Operation B is the best operation for minimizing the expected value, then performing Operation A would not yield any additional benefit. Therefore, even with the above replacement, the expected value does not change.
Expected value of the latter operation
Regarding the expected value of the latter operation, we can model it as follows.
Expected amount paid until a number 2 or greater appears
The probability of rolling the die times and still paying yen until a number 2 or greater appears is . Therefore, the expected amount paid is:
Reference: Convergence and divergence conditions for infinite geometric series and proofs, etc. | Manabitimes ↗
Expected value after a number 2 or greater appears
With probability , can be replaced by one of . Therefore, the expected value is:
Adding the two expected values above yields the second term inside the in the official explanation ↗.
Code#
Transition equation:
#define int long long
map<int, double>mp;
int a, x, y;
double f(int n) {
if (!n)return 0;
if (mp.count(n))return mp[n];
return mp[n] = min(x + f(n / a), 1.2 * y + 0.2 * (f(n / 2) + f(n / 3) + f(n / 4) + f(n / 5) + f(n / 6)));
}
void solve() {
int n;cin >> n >> a >> x >> y;
printf("%.10f", f(n));
}cppABC 351 D - Grid and Magnet#
There is a grid with rows and columns. Some cells (possibly zero) contain magnets. The state of the grid is represented by strings of length . If the -th character of is ”#”, it means there is a magnet in the cell at the -th row from the top and -th column from the left; if it is ”.”, it means the cell is empty.
Takahashi, wearing iron armor, can move on the grid as follows:
- If any of the cells vertically or horizontally adjacent to the current cell contains a magnet, he cannot move.
- Otherwise, he can move to any vertically or horizontally adjacent cell. However, he cannot leave the grid.
For each cell without a magnet, define its degree of freedom as the number of cells he can reach by repeatedly moving from that cell. Find the maximum degree of freedom among all cells without a magnet in the grid.
Here, in the definition of degree of freedom, “cells he can reach by repeatedly moving from that cell” refers to cells that can be reached from the starting cell via some sequence of moves (possibly zero moves). It is not necessary that there exists a single move sequence that visits all these reachable cells starting from the starting cell. Specifically, each cell itself (without a magnet) is always included in the cells reachable from that cell.
During the contest, the
cntarray was too small for this problem… it didn’t result in RE, but WA.
The overall idea is relatively simple: mark the cells around ’#’ as 1, mark visited paths as 2, so that each time you enter a connected component, you only visit cells marked 1/2 once. This allows calculating the number of traversable cells within each connected component.
char s[1010][1010];
int vis[1010][1010], dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};
void solve() {
int n, m;cin >> n >> m;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= m;j++) {
cin >> s[i][j];
if (s[i][j] == '#') {
if (i - 1 >= 1)vis[i - 1][j] = 1;
if (i + 1 <= n) vis[i + 1][j] = 1;
if (j + 1 <= m) vis[i][j + 1] = 1;
if (j - 1 >= 1) vis[i][j - 1] = 1;
}
}
}
int k = 0, ans = 1;
auto bfs = [&](int sx, int sy) {
map<pair<int, int>, int>once;
queue<pair<int, int>>q;q.push({sx,sy});vis[sx][sy] = 2;k++;
while (q.size()) {
auto [x, y] = q.front();q.pop();
for (int i = 0;i < 4;i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 1 || b<1 || a>n || b>m || vis[a][b] == 2 || s[a][b] == '#')continue;
if (vis[a][b] == 1) {
if (!once.count({a,b})) {
once[{a, b}] = 1; k++;
}
continue;
}
q.push({a,b});vis[a][b] = 2;k++;
}
}
};
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= m;j++) {
if (s[i][j] == '.' && !vis[i][j]) {
k = 0;
bfs(i, j);
ans = max(ans, k);
}
}
}
cout << ans << '\n';
}cppABC 351 E - Jump Distance Sum#
On the coordinate plane, there are points , where point has coordinates . The distance between two points and is defined as follows:
A rabbit initially located at point . A rabbit at position can jump to , , , or in one move. is defined as the minimum number of jumps required to reach point from point . If point cannot be reached from point after any number of jumps, set .
Calculate
Solution#
Similar problem: P3964 Squirrel Party ↗
Converting Chebyshev distance to Manhattan distance:
Chebyshev -> Manhattan
First, through a transformation that rotates by degrees around the origin and scales by a factor of , a point at moves to . Let the coordinates of the transformed points be , then we have and .
Next, we consider how the definition of changes. In the original definition, the rabbit can jump from to , , , and ; therefore, after transformation, it can jump from to , , , and . Substituting and , we get that it can jump from to , , , and . Thus, the minimum number of jumps from to is exactly the definition of (or if unreachable).
Next, we consider the problem after transformation. That is, let , define as above, and consider . Obviously, this gives the same answer as the original problem.
We further consider the case for , . If or , the rabbit cannot go from to , so . Otherwise, it will be exactly half of the Manhattan distance, i.e., .
After rotating and scaling by , it converts to Manhattan distance: From and , it follows that and have the same parity.
The points can be divided into two groups: those where and are both even, or both odd. For two points and belonging to different groups, ; for two different points belonging to the same group, .
Let be the set where are both even or both odd. Since the problem calculates pairwise sums, the order of calculation does not affect the result, so we can sort in descending order to simplify the calculation:
For : (similarly for )
#define int long long
void solve() {
int n;cin >> n;vector<int> a[4];
for (int i = 1;i <= n;i++) {
int x, y;cin >> x >> y;
if ((x + y) % 2 == 0) {
a[0].push_back(x + y);a[1].push_back(x - y);
} else {
a[2].push_back(x + y);a[3].push_back(x - y);
}
}
int ans = 0;
for (int i = 0;i < 4;i++) {
sort(a[i].rbegin(), a[i].rend());
for (int j = 0;j < a[i].size();j++) {
ans += a[i][j] * (a[i].size() - 1 - 2 * j);
}
}
cout << ans / 2 << '\n';
}cppABC 351 F - Double Sum#
You are given an integer sequence . Compute the following expression:
The constraints guarantee that the answer is less than .
Solution#
Fenwick Tree / Segment Tree / Sweep Line
Using Fenwick Tree to find the smallest element: Nowcoder ↗
Official Solution
Here, for a fixed , the contribution to the double sum can be expressed as
That is: for a fixed , we only need to know the sum of those on the right satisfying , and their count.
Using this fact, the problem can be solved by a sweep line algorithm as follows.
- Prepare a data structure that manages the following two values:
- A multiset supporting queries to retrieve the number of elements not less than .
- A multiset supporting queries to retrieve the sum of elements not less than .
- Also, prepare a variable to store the answer. Initially, let .
- For each , perform the following operations.
- Query with and name the response value .
- Query with and name the response value .
- Add to .
- Insert into and .
- Print the resulting value of .
and can be implemented using Fenwick Trees and coordinate compression; they can handle each query in time.
During implementation, compress all coordinates, then use two Fenwick trees: one maintains counts, the other maintains the sum of values. Scan in reverse order, each time querying “the count and sum of elements on the right not less than ”, then insert the current value.
class FenwickTree {
private:
vector<long long> bit; // 1-indexed
int n;
public:
FenwickTree(int n) {
this->n = n;
bit.assign(n + 1, 0);
}
FenwickTree(vector<int> a) : FenwickTree(a.size()) {
for (size_t i = 0; i < a.size(); i++)add(i + 1, a[i]);
}
long long sum(int i) {
long long ans = 0;
while (i)ans += bit[i], i -= i & -i;
return ans;
}
long long sum(int l, int r) {
return sum(r) - sum(l - 1);
}
void add(int i, int delta) {
while (i <= n)bit[i] += delta, i += i & -i;
}
};
int main() {
int N;
cin >> N;
vector<int> A(N);
for (int& x : A) cin >> x;
vector<int> B = A;
sort(B.begin(), B.end());
B.erase(unique(B.begin(), B.end()), B.end());
int M = B.size();
FenwickTree sum0(M), sum1(M);
long long ans = 0;
for (int i = N - 1; i >= 0; --i) {
int k = lower_bound(B.begin(), B.end(), A[i]) - B.begin() + 1;
long long c = sum0.sum(k, M);
long long s = sum1.sum(k, M);
ans += s - c * A[i];
sum0.add(k, 1);
sum1.add(k, A[i]);
}
cout << ans << '\n';
}cppABC 357 D - 88888888#
For a positive integer , let be the integer formed by concatenating exactly times. More precisely, treating as a string, concatenating copies of it, and interpreting the result as an integer yields .
Find
Consider whether you can derive the geometric series formula…1
Knowing the geometric series formula solves it.
Note: Here , which exceeds the range of long long. You can either cast to int128 or calculate and separately. Here, we directly use int128.
void solve() {
int n;cin >> n;
int s = to_string(n).size();
int x = n % mod * (qpow(10, (__int128_t)s * n) % mod - 1) % mod;x %= mod;
int y = qpow(10, s) - 1;y %= mod;
cout << x * qpow(y, mod - 2) % mod << '\n';
}cppABC 357 E - Reachability in Functional Graph#
There is a directed graph with vertices numbered to and edges. Each vertex has outdegree , and the edge from vertex points to vertex . Count the number of ordered pairs of vertices such that vertex is reachable from vertex .
Here, vertex is reachable from vertex if there exists a sequence of vertices of length satisfying the following conditions. Note that if , it is always reachable.
- .
- .
- For each , there is an edge from vertex to vertex .
Solution#
Functional Graph / Topological Sort / SCC Compression
A directed graph with vertices and edges forms a functional graph, which must contain exactly one cycle per weakly connected component. Breaking the cycle and case analysis is a common technique for functional graphs.
Method 1: Topological Sort#
This problem can be divided into three cases:
- For a chain: let the number of nodes in the chain be , then the number of pairs is
- For a cycle: let the number of nodes in the cycle be , then the number of pairs is
- For the parts combining chains and cycles: the additional number of pairs is
Summing up the contributions from each part gives the answer.
Example diagram:

Code: First perform topological sorting to calculate the contributions from the chains. For the cycles, calculate the “number of nodes in the cycle” and the “number of nodes that can reach the cycle”.
#define int long long
void solve() {
int n;cin >> n;
vector<int> to(n + 1), in(n + 1);
for (int i = 1;i <= n;i++) {
cin >> to[i];
++in[to[i]];
}
queue<int>q;
for (int i = 1;i <= n;i++) {
if (!in[i])q.push(i);
}
int ans = 0;
vector<int>f(n + 1);
while (q.size()) {
auto x = q.front();q.pop();
++f[x];
f[to[x]] += f[x];
ans += f[x];
if (--in[to[x]] == 0){
q.push(to[x]);
}
}
vector<int> vis(n + 1);
for (int i = 1;i <= n;i++) {
if (in[i] > 0) {
int cnt = 0, sum = 0;
for (int j = i;!vis[j];j = to[j]) {
++cnt;
sum += f[j];
vis[j] = 1;
}
ans += cnt * (cnt + sum);
}
}
cout << ans << '\n';
}cppMethod 2: SCC Compression#
First, find the cycles. After finding a cycle, compress it into a single node. This makes the graph easier to handle.
The weight of this compressed cycle node is the number of nodes in the cycle. For other nodes, the weight is 1.
This graph then becomes a Directed Acyclic Graph (DAG). We only need to use memoized search to solve the problem.
Code:
#define int long long
void solve() {
int n;cin >> n;
vector<int> a(n + 1), vis(n + 1), f(n + 1);
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++) {// Steps to find cycles
if (vis[i])continue;
int u = i;
while (1) {
if (vis[u]) {
if (vis[u] == i) {
int x = a[u], cnt = 1;
while (x != u) {
x = a[x];cnt++;
}
f[u] = cnt;
x = a[u];
while (x != u) {
f[x] = cnt;x = a[x];
}
}
break;
}
vis[u] = i;
u = a[u];
}
}
int ans = 0;
auto dfs = [&](auto self, int x) {
if (f[x])return f[x];
return f[x] = self(self, a[x]) + 1;
};
for (int i = 1;i <= n;i++)ans += dfs(dfs, i);
cout << ans << '\n';
}cppABC 357 F - Two Sequence Queries#
You are given sequences and of length . You are also given queries, which need to be processed in order.
There are three types of queries:
1 l r x: Add to the interval of array .2 l r x: Add to the interval of array .3 l r: Query .
Solution#
Segment Tree
Classic Segment Tree Problem
For a single point , adding yields:
Then .
For an interval , adding to and to yields:
.
Thus we only need to maintain 4 pieces of information: , , , and .
For and themselves, , .
We need arrays sa, sb, sab to store , , , and lazy tags ta, tb.
Code: (Two coding styles: using a struct (I find this more convenient) and using direct arrays)
#define lc u<<1
#define rc u<<1|1
constexpr int mod = 998244353, N = 2e5 + 10;
int a[N], b[N];
struct Tree {
int l, r, sa, sb, sab, ta, tb;
}tr[N << 2];
void pushup(int u) {
tr[u].sa = (tr[lc].sa + tr[rc].sa) % mod;
tr[u].sb = (tr[lc].sb + tr[rc].sb) % mod;
tr[u].sab = (tr[lc].sab + tr[rc].sab) % mod;
}
void ca(int u, int x) {
tr[u].sa = (tr[u].sa + x * (tr[u].r - tr[u].l + 1)) % mod;
tr[u].sab = (tr[u].sab + x * tr[u].sb) % mod;
tr[u].ta = (tr[u].ta + x) % mod;
}
void cb(int u, int x) {
tr[u].sb = (tr[u].sb + x * (tr[u].r - tr[u].l + 1)) % mod;
tr[u].sab = (tr[u].sab + x * tr[u].sa) % mod;
tr[u].tb = (tr[u].tb + x) % mod;
}
void pushdown(int u) {
ca(lc, tr[u].ta);
ca(rc, tr[u].ta);
tr[u].ta = 0;
cb(lc, tr[u].tb);
cb(rc, tr[u].tb);
tr[u].tb = 0;
}
void build(int u, int l, int r) {
tr[u] = {l,r,a[l],b[l],a[l] * b[l] % mod,0,0};
if (l == r)return;
int m = l + r >> 1;
build(lc, l, m);
build(rc, m + 1, r);
pushup(u);
}
void modify1(int u, int l, int r, int x) {
if (l <= tr[u].l && tr[u].r <= r) {
ca(u, x);
return;
}
int m = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= m) modify1(lc, l, r, x);
if (r > m) modify1(rc, l, r, x);
pushup(u);
}
void modify2(int u, int l, int r, int x) {
if (l <= tr[u].l && tr[u].r <= r) {
cb(u, x);
return;
}
int m = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= m) modify2(lc, l, r, x);
if (r > m) modify2(rc, l, r, x);
pushup(u);
}
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].sab;
int m = tr[u].l + tr[u].r >> 1;
pushdown(u);
int sum = 0;
if (l <= m) sum = (sum + query(lc, l, r)) % mod;
if (r > m) sum = (sum + query(rc, l, r)) % mod;
return sum;
}
void solve() {
int n, q;cin >> n >> q;
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++)cin >> b[i];
build(1, 1, n);
while (q--) {
int op, l, r;cin >> op >> l >> r;
if (op == 1) {
int x;cin >> x;
modify1(1, l, r, x);
} else if (op == 2) {
int x;cin >> x;
modify2(1, l, r, x);
} else {
cout << query(1, l, r) << '\n';
}
}
}cppABC 358 C - Popcorn#
In AtCoder Land, there are popcorn stalls numbered to . They sell different flavors of popcorn, labeled , but not every stall sells all flavors. .
Takahashi obtained information about which flavors each stall sells. This information is represented by strings of length . If the -th character of is “o”, it means stall sells flavor . If it is “x”, it means stall does not sell flavor . Each stall sells at least one flavor, and each flavor is sold by at least one stall.
Takahashi wants to taste all flavors of popcorn but doesn’t want to walk too much. Find the minimum number of stalls Takahashi must visit to purchase all flavors of popcorn.
Based on the constraints, a direct binary enumeration suffices.
void solve() {
int n, m;cin >> n >> m;
vector<string> s(n);
for (int i = 0;i < n;i++) {
cin >> s[i];
}
int ans = 1e9;
for (int i = 1;i < (1 << n);i++) {
vector<int> vis(m);
for (int j = 0;j <= __lg(i);j++) {
if (((i >> j) & 1)) {
for (int k = 0;k < m;k++) {
if (s[j][k] == 'o') {
vis[k] = 1;
}
}
}
}
int ok = 1;
for (int j = 0;j < m;j++) {
if (!vis[j])ok = 0;
}
if (ok)ans = min(ans, __builtin_popcount(i));
}
cout << ans << '\n';
}cppABC 358 E - Alphabet Tiles#
Find the number of strings consisting of uppercase English letters with lengths in that satisfy the following conditions (modulo ):
- For each integer satisfying , the following condition holds:
- corresponds to the -th letter of the alphabet (
a[i] = 'a' + i - 1). - The number of occurrences of in the string is between and inclusive.
- corresponds to the -th letter of the alphabet (
Solution#
DP + Combinations, This is a classic problem: ABC234_F ↗
Classic — basically the same as P1077 “Arranging Flowers”
represents the number of ways to form a string of length using the first types of letters.
Transition equation:
A string of length using the first types of letters can be formed from a string of length using the first types by adding copies of the -th character. The positions for these new characters can be chosen in ways.
#define int long long
constexpr int mod = 998244353;
int a[27], f[27][1010], c[1010][1010];
void solve() {
int n;cin >> n;
for (int i = 1;i <= 26;i++)cin >> a[i];
for (int i = 0;i <= n;i++)c[i][0] = 1, c[i][1] = i;
for (int i = 2;i <= n;i++) {
for (int j = 2;j <= n;j++) {
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];c[i][j] %= mod;
}
}
f[0][0] = 1;
for (int i = 1;i <= 26;i++) {
for (int j = 0;j <= n;j++) {
for (int k = 0;k <= min(a[i], j);k++) {
f[i][j] += f[i - 1][j - k] * c[j][k];f[i][j] %= mod;
}
}
}
int ans = 0;
for (int i = 1;i <= n;i++) {
ans += f[26][i];ans %= mod;
}
cout << ans << '\n';
}cppF - Easiest Maze
Snuke plans to build a maze as a new attraction in AtCoder Land. The maze is a grid with rows and columns. The top side of the top-right cell is the entrance, and the bottom side of the bottom-right cell is the exit. He will create the maze by appropriately placing walls between adjacent cells.
He likes simple mazes, so he wants the path from the entrance to the exit to pass through exactly cells and have no branches. Determine if it is possible to create such a maze, and if possible, construct one.
For example, in the figure below, and , and walls are placed on the solid lines (walls are always placed on the periphery except for the entrance and exit). In this case, the path from the entrance to the exit passes through exactly cells and has no branches.

Below is a formal description.
There is a grid with rows and columns. Let denote the cell located at the -th row from the top and -th column from the left. For each pair of edge-adjacent cells, you can decide whether or not to place a wall between them. Determine if it is possible to place walls to satisfy the following conditions, and if possible, construct such a placement.
Consider an undirected graph with vertices. Each vertex of is uniquely labeled by a pair of integers . Two distinct vertices and are connected by an edge if and only if and there is no wall between the corresponding cells and on the grid.
Condition: There exists a simple path connecting the two vertices and that has vertices, and the connected component containing the vertices and consists solely of this path.
ABC 358 G - AtCoder Tour#
AtCoder Land is represented by a grid with rows and columns. Let denote the cell at the -th row and -th column.
Takahashi starts at cell and repeats the following operation times:
- He either stays in the current cell or moves to an adjacent cell. After this operation, if he is at cell , he gains fun value .
Find the maximum total fun value he can obtain.
Solution#
Time Complexity:
#define int long long
int dx[] = {1, 0, -1, 0, 0};
int dy[] = {0, 1, 0, -1, 0};
void solve() {
int n, m, K;
cin >> n >> m >> K;
int Si, Sj;
cin >> Si >> Sj;
Si--;
Sj--;
vector<vector<int>> A(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> A[i][j];
}
}
int N = min(n * m, K);
vector<vector<vector<int>>> dp(N + 1, vector<vector<int>>(n, vector<int>(m, INT_MIN)));
dp[0][Si][Sj] = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < m; k++) {
for (int l = 0; l < 5; l++) {
int x = j + dx[l];
int y = k + dy[l];
if (0 <= x && x < n && 0 <= y && y < m) {
dp[i + 1][x][y] = max(dp[i + 1][x][y], dp[i][j][k] + A[x][y]);
}
}
}
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ans = max(ans, dp[N][i][j] + (K - N) * A[i][j]);
}
}
cout << ans << '\n';
}cppABC 360 E - Random Swaps of Balls#
There are white balls and one black ball. These balls are arranged in a row, with the black ball initially at the leftmost position.
Takahashi will perform the following operation times.
- Choose an integer uniformly at random from twice. Let and be the chosen integers. If , swap the balls at positions and .
After operations, let the black ball be at the -th position from the left. Find the expected value of modulo .
Solution#
Combinatorics / Probability DP
Each operation has probability: of not changing the position (i.e., staying at ), and of moving to position (where ).
That is, before the -th operation, to calculate the probability of being in position , it either stays there, or is not in position and moves into position . There are only two options.
State transition equation: .
The calculated represents the probability of still being in position after rounds, which is exactly . Substitute into the formula:
Be very careful about modulo operations in various situations to avoid making low-level mistakes!
void solve() {
int n, k;cin >> n >> k;
int p = (n * n - 2 * (n - 1)) % mod * inv(n * n % mod) % mod;
int q = 2 * inv(n * n % mod) % mod;
vector<int> f(k + 1);
f[0] = 1;
for (int i = 1;i <= k;i++) {
f[i] = (f[i - 1] * p % mod + (1 - f[i - 1] + mod) * q % mod) % mod;
}
cout << (2 + n * (1 - f[k] + mod) % mod) % mod * inv(2) % mod << '\n';
}cppEasier to understand approach: Same idea as above, both aim to calculate .
Let be the probability of being in / not in position after operations.
It’s easy to see that before any operation, .
If after an operation we are in position , there are two possibilities:
- We were in position and the position did not change.
- We were not in position and the swap moved us to position .
.
If after an operation we are not in position , there are two possibilities:
- We were in position and the position changed.
- We were not in position , and the move did not result in being in position (i.e., we either stayed in another position or moved to another non-1 position).
.
void solve() {
int n, k;cin >> n >> k;
int p = (n * n - 2 * (n - 1)) % mod * inv(n * n % mod) % mod;
int q = 2 * inv(n * n % mod) % mod;
vector<array<int, 2>> f(k + 1);// f[i][0/1] represents probability of being in/not in position 1 after i operations
f[0][0] = 1;
for (int i = 1;i <= k;i++) {
f[i][0] = f[i - 1][0] * p % mod + f[i - 1][1] * q % mod;
f[i][0] %= mod;
f[i][1] = f[i - 1][0] * (1 - p + mod) % mod + f[i - 1][1] * (1 - q + mod) % mod;
f[i][1] %= mod;
}
cout << (2 + n * (1 - f[k][0] + mod) % mod) % mod * inv(2) % mod << '\n';
}cppFootnotes#
-
Geometric series summation formula: ↩