π§© AtCoder Beginner Contest 403
π§© AtCoder Beginner Contest 403
# Info & Summary
- Date:
2025-04-27
- Completion Status: A β / B β / C β / D β / E β / F β / G β
- Problem Type:
Problem | Type(s) | Data Structure / Algo | π₯ Key Insight / Mark |
---|---|---|---|
D | Maximum Independent Set on a Path | Path DP/DP | βπ₯ |
E | Trie | Trie | ππ§ βπ₯ |
F | String Constructive | DP | π |
G | Dynamic Segment Tree | Dynamic Segment Tree | π |
Details
Emoji | Label | Meaning |
---|---|---|
β | Important | Problem is strategically useful or has reusable patterns |
π₯ | Must master | Classic problem or technique that appears often and is worth mastering |
β οΈ | Reattempt | You attempted but failed; worth reviewing and solving again |
π§ | New to me | Introduced a new idea, technique, or pattern you hadnβt seen before |
π οΈ | Template | You built or improved a code template from solving this problem |
π | Too hard now | Beyond current ability to understand/solve; revisit after more practice |
π A - Odd Position Sum

#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int ans = 0;
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
ans += i & 1 ? 0 : num;
}
cout << ans << endl;
return 0;
}
π B - Four Hidden

Details
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int ans = 0;
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
ans += i & 1 ? 0 : num;
}
cout << ans << endl;
return 0;
}
// Better version
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string T, U;
cin >> T >> U;
for (int i = 0; i <= T.size() - U.size(); i++)
{
bool ok = true;
for (int j = 0; j < U.size(); j++)
{
if (T[i + j] != U[j] && T[i + j] != '?')
{
ok = false;
break;
}
}
if (ok)
{
cout << "Yes" << endl;
return 0;
}
}
cout << "No" << endl;
return 0;
}
π C - 403 Forbidden

#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M, Q;
cin >> N >> M >> Q;
map<int, set<int>> mp;
while (Q--)
{
int a;
cin >> a;
if (a == 1)
{
int x, y;
cin >> x >> y;
mp[x].insert(y);
}
else if (a == 2)
{
int x;
cin >> x;
mp[x].insert(-1);
}
else
{
int x, y;
cin >> x >> y;
if (mp[x].count(-1) == 1 || mp[x].count(y) == 1)
{
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
return 0;
}
π D - Forbidden Difference

If
If , then , so it is sufficient to exclude repeated values in
If has distinct values, each of them can be retained only once, so can have a length of at most . Thus, at least operations are required
Tips
dp0
: The maximum number of elements kept if the previous number was NOT selecteddp1
: The maximum number of elements kept if the previous number WAS selected
At every number , we have two options:
- Select
- Skip
DP Transition
- Case 1:
abs(A[l] - lst) == 0
Current number and last selected number differ by exactly D -> Conflict happens -> We must skip the current one OR select the current one but skip the last one
long long new_dp0 = max(dp0, dp1);
long long new_dp1 = dp0 + count;
dp0 = new_dp0;
dp1 = new_dp1;
- Case 2:
abs(A[l] - lst) != D
Current number and last number do NOT differ by D -> No conflict -> We can freely choose to select or not select the current number
dp0 = max(dp0, dp1);
dp1 = dp0 + count;
// Better version
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, D;
cin >> N >> D;
vector<int> A(N);
for (int i = 0; i < N; i++)
{
cin >> A[i];
}
// If D = 0, the problem becomes "remove duplicates"
if (D == 0)
{
sort(A.begin(), A.end());
cout << A.end() - unique(A.begin(), A.end()) << endl;
return 0;
}
// Group numbers by their value modulo D
// Inside each group, sort numbers by their actual value ascending
// - Numbers with the same mod D do not conflict
// - Conflicts happen across different groups, especially adjacent numbers differing exactly by D
sort(A.begin(), A.end(), [&](int i, int j)
{ return pair(i % D, i) < pair(j % D, j); });
// dp0 = maximum number of elements kept if the previous number is not selected
// dp1 = maximum number of elements kept if the previous number is selected
long long dp0 = 0, dp1 = 0;
// lst = last processed number (initialized to avoid false conflict at the start)
int lst = -D - 1;
for (int l = 0, r = 0; l < N; l = r)
{
// Process consecutive blocks of identical numbers [l, r)
while (r < N && A[l] == A[r])
{
r++;
}
// r - l = number of times the value A[l] appears
int count = r - l;
if (abs(A[l] - lst) == D)
{
// Conflict: cannot pick both
// dp0 = max(dp0, dp1) (skip current)
// dp1 = dp0 (before skip) + (count of current number) (pick current block)
long long new_dp0 = max(dp0, dp1);
long long new_dp1 = dp0 + count;
dp0 = new_dp0;
dp1 = new_dp1;
}
else
{
// No conflict: just a normal transition
// dp0 = max(dp0, dp1) (best so far without selecting)
// dp1 = dp0 + (count of current number) (choose current freely)
dp0 = max(dp0, dp1);
dp1 = dp0 + count;
}
lst = A[l];
}
cout << N - max(dp0, dp1) << endl;
return 0;
}
π E - Forbidden Prefix

Warning
Hard Problems to Tackle Later
Tips
This problem is an exercise of trie
, which is a data structure that represents a list of strings as a rooted tree. Information stored on each vertex allows us efficient manipulations regarding prefixes
A trie is constructed so that each vertex represents a character, and a path from the root to another vertex corresponds to a string. Strings with common prefixes share a path from the root to some of their ancestor
Each vertex of the trie maintain the following information:
- The character represented by the vertex
- The list of children of the vertex
- The list of strings that is accepted at the vertex; i.e. the list of indices of the original list, that coincides with the string represented by the path from the root to the vertex
We maintain all the given strings in a single trie , regardless of which of and it will belong to
Also, maintain a set , which stores "the strings contained in that has an element in as a prefix"
The sought answer is:
Processing query 1
If the subtree rooted at the vertex accepting contains a vertex that accepts , then we add to
We keep for each vertex in , the set of the elements to be added to when an accepted by is added for the next time. This can be updated by, every time adding to , for all the vertex that accepts a prefix of , adding to When adding , add all to and empty , where is the vertex accepting . Since can have up to elements, so it seems inefficient at a glance, but the total number of times that is taken out of any is at most times. Therefore, the overall time complexity is
Processing query 2
It is sufficient to check if there is a vertex that accepts a prefix of and also accepts an
We maintain a flag for each vertex in signifying whether contains a string that is accepted by that vertex
When adding a string of to , we set for the vertex accepting that string
When adding a string of to , we check if there exists an ancestor of the vertex that accepts its prefix such that
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 5e5 + 10;
int tot = 1;
// p is node id, trie[p][c] is next node via character c
int trie[N][26];
// number of Type 2 strings passing through this node
int cnt[N];
// whether this node is "blocked" (i.e., a prefix has been inserted by Type 1)
bool vis[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int Q;
cin >> Q;
int ans = 0;
for (int i = 0; i < Q; i++)
{
int T;
string S;
cin >> T >> S;
// Insert into the Trie and mark nodes `vis`
// and adjust `cnt` to remove invalid paths
if (T == 1)
{
// insert S into the Trie
int p = 1;
for (auto c : S)
{
int &q = trie[p][c - 'a'];
if (!q)
{
tot++;
q = tot;
}
p = q;
// If during insertion you reach a node that is already
// path already blocked
if (vis[p])
{
break;
}
}
// If the endpoint wasn't already blocked
if (!vis[p])
{
// block the node
vis[p] = true;
// Find how many Type 2 strings were passing through here
int d = cnt[p];
ans -= d;
// Then decrement all counts along the prefix by d
p = 1;
for (auto c : S)
{
cnt[p] -= d;
p = trie[p][c - 'a'];
}
}
}
// Insert into the Trie normally, increment counters
// unless the path is blocked by a `vis` node
else
{
// insert S into the Trie
int p = 1;
for (auto c : S)
{
int &q = trie[p][c - 'a'];
if (!q)
{
q = ++tot;
}
p = q;
// If during insertion you reach a node that is already
// path already blocked
if (vis[p])
{
break;
}
}
// If you reach the end without hitting a blocked node
if (!vis[p])
{
// Increment the answer ans++ because this is a valid new Type 2 string
ans++;
// Update counters cnt[p]++ along the path
p = 1;
cnt[p]++;
for (auto c : S)
{
p = trie[p][c - 'a'];
cnt[p]++;
}
}
}
cout << ans << endl;
}
return 0;
}
π F - Shortest One Formula

Warning
Hard Problems to Tackle Later
// TODO
π G - Odd Position Sum Query

Warning
Hard Problems to Tackle Later
// TODO