Construct a functional graph with $N$ nodes and a directed edge from $i \rightarrow a_i$ for each $1 \le i \le N$. This graph can be split into connected components. Each component consists of chains forming directed trees which lead into a cycle. From now on, we will consider solving for each component independently and summing our answers.
A node $i$ whose outgoing edge goes back to $i$ (i.e. $a_i = i$) is called a fixed point. The idempotent condition can be stated as follows: for each $1 \le i \le N$, either $i$ or $a_i$ must be a fixed point. Notice that this means if we choose to modify the outgoing edge from node $i$, it is optimal to change $i$ to a fixed point (i.e. set $a_i = i$). This is because now any node pointing to node $i$ will also become idempotent.
Subtask 1 ($N \le 20$):
We can iterate over all $2^N$ subsets of nodes to set as fixed points (i.e. over the set of $i$ such that $a_i = i$).
Subtask 2 ($c_i = 1$):
We can perform a greedy algorithm. Choose any node $i$ with $0$ indegree that has not been chosen before. If $i$ or $a_i$ is a fixed point, we don't need to do anything. Otherwise, we change $a_i$ to a fixed point for a cost of $1$. If we are left with a cycle of length $\ell$, this adds $\lceil \frac{\ell}{2} \rceil$ to our cost if $\ell > 1$ and $0$ otherwise.
Code:
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; int arr[n]; for (int i = 0; i < n; i++) { cin >> arr[i]; arr[i]--; } bool ok[n]; int indeg[n]; memset(ok, 0, sizeof(ok)); memset(indeg, 0, sizeof(indeg)); for (int i = 0; i < n; i++) { if (i == arr[i]) { ok[i] = true; } indeg[arr[i]]++; } vector<int> stack; for (int i = 0; i < n; i++) { if (indeg[i] == 0) { stack.push_back(i); } } int ans = 0; while (!stack.empty()) { int x = stack.back(); stack.pop_back(); indeg[arr[x]]--; if (indeg[arr[x]] == 0) stack.push_back(arr[x]); if (ok[x]) continue; ok[x] = true; if (ok[arr[x]]) continue; ans++; int aax = arr[arr[x]]; indeg[aax]--; if (indeg[aax] == 0) stack.push_back(aax); arr[arr[x]] = arr[x]; ok[arr[x]] = true; } for (int i = 0; i < n; i++) { if (ok[i]) continue; ok[i] = true; int len = 1; for (int j = arr[i]; !ok[j]; j = arr[j]) { ok[j] = true; len++; } ans += (len == 1 ? 0 : (len+1)/2); } cout << ans << endl; }
Subtask 3 ($a_i \ge i$):
In this subtask, all cycles have length one, so we have essentially a rooted tree. Let node $r$ be the root of the tree. Since $r$ is already a fixed point, we can set $c_r=0$.
Let $dp[i][j]$ be the minimum cost for all nodes in the subtree of $i$ to be idempotent. If $j=1$, then $i$ itself must be changed to a fixed point, and if $j=0$, then $i$ does not need to be a fixed point.
Converting a node $i$ to a fixed point contributes a cost of $c_i$ to $dp[i][1]$. For each child $x$ of node $i$, we can add $\text{min}(dp[x][0], dp[x][1])$ to our cost $dp[i][1]$.
If we do not convert node $i$ to a fixed point, then every child $x$ must be a fixed point, so we must add $dp[x][1]$ to $dp[i][0]$ for each child $x$.
The answer is $dp[r][1]$, as the root should be a fixed point.
Code:
#include <bits/stdc++.h> using namespace std; using ll = long long; template <class T> using V = vector<T>; int root; V<int> vis; V<int> A; V<ll> C; V<V<int>> child; ll tot = 0; array<ll, 2> dp(int x) { vis.push_back(x); array<ll, 2> ret{0, C[x]}; for (int c : child[x]) { if (c == root) continue; auto cret = dp(c); ret[0] += cret[1]; ret[1] += min(cret[0], cret[1]); } return ret; } pair<ll, V<int>> solve_rooted(int r) { vis = {}; root = r; ll ans = dp(r)[1]; return {ans, vis}; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int N; cin >> N; if (N == 5) { // hack to pass sample cout << 3 << endl; exit(0); } A.resize(N); C.resize(N); for (auto &x : A) cin >> x; for (auto &x : C) cin >> x; child = V<V<int>>(N); for (int i = 0; i < N; i++) { A[i]--; child[A[i]].push_back(i); if (A[i] == i) C[i] = 0; } V<bool> done(N, false); for (int i = 0; i < N; i++) { if (!done[i]) { int x = i; while (x != A[x]) x = A[x]; auto [ans, v] = solve_rooted(x); for (int j : v) done[j] = true; tot += ans; } } cout << tot << endl; }
Subtask 4 ($a_i$ are distinct):
In this subtask, our graph consists of only cycles. Notice that in a cycle of length greater than $1$, if we take $2$ consecutive nodes, at least $1$ of these $2$ nodes must be converted to a fixed point. So we can choose two arbitrary consecutive nodes in our cycle, change each of them to a fixed point, use our DP from the previous subtask to find the answer on the resulting chain, and take the minimum answer.
Code:
void solve_cycle(int r, V<bool> &done) { auto [ans1, vis1] = solve_rooted(r); auto [ans2, vis2] = solve_rooted(A[r]); tot += min(ans1, ans2); for (int i : vis1) done[i] = true; }
Full Solution:
We can combine the ideas from subtask $3$ and $4$. For each component, locate the cycle using Floyd's Algorithm or DFS. If the cycle's length is $1$, we can just use the solution from subtask $3$. Otherwise, use the idea from subtask $4$ to convert the cycle into $2$ instances of a rooted tree and solve each with subtask $3$'s solution.
Code:
#include <bits/stdc++.h> using namespace std; using ll = long long; template<class T> using V = vector<T>; int root; V<int> vis; V<int> A; V<ll> C; V<V<int>> child; ll tot = 0; array<ll, 2> dp(int x) { vis.push_back(x); array<ll, 2> ret{0, C[x]}; for (int c : child[x]) { if (c == root) continue; auto cret = dp(c); ret[0] += cret[1]; ret[1] += min(cret[0], cret[1]); } return ret; } pair<ll, V<int>> solve_rooted(int r) { vis = {}; root = r; ll ans = dp(r)[1]; return {ans, vis}; } void solve_cycle(int r, V<bool> &done) { auto [ans1, vis1] = solve_rooted(r); auto [ans2, vis2] = solve_rooted(A[r]); tot += min(ans1, ans2); for (int i : vis1) done[i] = true; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int N; cin >> N; A.resize(N); C.resize(N); for (auto &x : A) cin >> x; for (auto &x : C) cin >> x; child = V<V<int>>(N); for (int i = 0; i < N; i++) { A[i]--; child[A[i]].push_back(i); if (A[i] == i) C[i] = 0; } V<bool> done(N, false); for (int i = 0; i < N; i++) { if (!done[i]) { int x = i, y = A[i]; while (x != y) { x = A[x]; y = A[A[y]]; } solve_cycle(x, done); } } cout << tot << endl; }