PROBLEM LINK:
Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4
Author: raysh07
Tester: raysh07
Editorialist: iceknight1093
DIFFICULTY:
Easy
PREREQUISITES:
Dynamic programming
PROBLEM:
You’re given an array A. You can perform the following operation:
- Choose 1 \leq i \leq j \leq N such that A_i = A_j.
Then, for each index k \in [i, j], set A_k to A_i.
Some elements of A are zeros, meaning they’re undefined.
Across all possible replacements of the zeros, find the minimum possible number of distinct elements in A after performing this operation several times.
EXPLANATION:
Recall from the solution to the easy version that if there were no zeros, the problem could be solved in \mathcal{O}(N) time with a relatively simple DP, which works as follows:
- dp_i is the answer for the prefix ending at i.
- \text{best}[x] is the minimum value of dp_{j-1} across all j with A_j = x so far.
- Then, at each i, we first minimize \text{best}[A_i] with dp_{i-1}, and then set dp_i to 1 + \text{best}[A_i].
Let’s attempt to adapt this solution to this version when there are zeros.
The definitions of dp_i and \text{best}[x] don’t change.
Let’s look at index i.
If A_i = 0, we can immediately set dp_i = 1, because we can always just fix this 0 to be equal to A_1 and then cover the entire prefix.
If A_i \neq 0, we can, just as in the version without zeros, update \text{best}[A_i] and then set dp_i to \text{best}[A_i] + 1.
This takes care of the case where the segment ending at i starts at some existing occurrence of A_i.
However, now that there are zeros, we have another choice: we can choose any previous zero, set it to A_i, and then color the segment from this zero to A_i.
If the zero chosen is at index j, this gets us a value of dp_{j-1} + 1.
To speed this up, note that we can just do the exact same thing: let \text{best}[0] be the minimum value of dp_{j-1} across all positions with A_j = 0, then the best we can do is \text{best}[0] + 1.
This allows us to augment our solution quite easily, resulting in this as the final algorithm:
- If A_i = 0, dp_i = 1.
- Otherwise, dp_i = \min(\text{best}[A_i], \text{best}[0]) + 1.
- Make sure to keep the \text{best} array updated appropriately.
TIME COMPLEXITY:
\mathcal{O}(N) per testcase.
CODE:
Author's code (C++)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)1e18
mt19937_64 RNG(chrono::steady_clock::now().time_since_epoch().count());
void Solve()
{
int n; cin >> n;
vector <int> a(n + 1);
for (int i = 1; i <= n; i++){
cin >> a[i];
}
vector <int> dp(n + 1, INF);
dp[0] = 0;
vector <int> e(n + 1, INF);
int X = INF;
int mn = INF;
for (int i = 1; i <= n; i++){
// transport dp[i - 1] to e[a[i]]
if (a[i] == 0){
X = min(X, dp[i - 1]);
mn = min(mn, X);
} else {
e[a[i]] = min(e[a[i]], dp[i - 1]);
mn = min(mn, e[a[i]]);
}
// end something here
if (a[i] == 0){
dp[i] = min(dp[i], mn + 1);
} else {
dp[i] = min(dp[i], min(X, e[a[i]]) + 1);
}
}
cout << dp[n] << "\n";
}
int32_t main()
{
auto begin = std::chrono::high_resolution_clock::now();
ios_base::sync_with_stdio(0);
cin.tie(0);
int t = 1;
// freopen("in", "r", stdin);
// freopen("out", "w", stdout);
cin >> t;
for(int i = 1; i <= t; i++)
{
//cout << "Case #" << i << ": ";
Solve();
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
cerr << "Time measured: " << elapsed.count() * 1e-9 << " seconds.\n";
return 0;
}
Editorialist's code (PyPy3)
for _ in range(int(input())):
n = int(input())
a = list(map(int, input().split()))
dp = [0]*n
best = [n+1]*(n+1)
for i in range(n):
if i > 0: best[a[i]] = min(best[a[i]], dp[i-1])
else: best[a[i]] = 0
if a[i] == 0:
dp[i] = 1
else:
dp[i] = min(best[0], best[a[i]]) + 1
print(dp[n-1])