解析
首先如果考虑是树的情况那建完反图就跑一个dp就完事了。设 (dp[x][0/1]) 代表节点 (x) 选还是不选。有转移方程:(dp[x][0]=sum_{yin son(x)} max(dp[y][0],dp[y][1])),(dp[x][1]=dp[x][0]-(min_{yin son(x)} max(dp[y][0],dp[y][1]))+1)。
但这是一道基环树,其实差不多。首先基环树的套路就是先找到还然后再在环上处理。考虑是强行断环还是复制一遍。
这道题应该是强行断环,假设断的是 ((s,t)) 这一条边(这里是原图),那么分两种情况讨论。
-
s一定不选。这种情况碰到t的时候(dp[t][1])就直接等于(dp[t][0]+1)就好了。最后答案是(dp[s][0])。
-
s一定选。这种情况就直接正常跑就好了。最后答案是(dp[s][1])。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
template <typename T> void read(T &x) {
T f = 1;
char ch = getchar();
for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
x *= f;
}
int n;
int fa[MAXN];
int u, v;
vector<int> vec[MAXN];
bool vis[MAXN];
int dp[MAXN][2];
int ans;
void get_loop(int x) {
if (vis[x]) {
u = x;
v = fa[x];
return;
}
vis[x] = true;
get_loop(fa[x]);
}
void dfs(int x, bool zsy) {
dp[x][0] = dp[x][1] = 0;
vis[x] = 1;
for (int i = 0; i < (int)vec[x].size(); i++) {
int y = vec[x][i];
if (y != u) {
dfs(y, zsy);
dp[x][0] += max(dp[y][0], dp[y][1]);
}
}
if (zsy) {
if (x == v) {
dp[x][1] = dp[x][0] + 1;
} else {
for (int i = 0; i < (int)vec[x].size(); i++) {
int y = vec[x][i];
if (y != u) {
dp[x][1] = max(dp[x][1], dp[x][0] - max(dp[y][0], dp[y][1]) + dp[y][0] + 1);
}
}
}
} else {
for (int i = 0; i < (int)vec[x].size(); i++) {
int y = vec[x][i];
if (y != u) {
dp[x][1] = max(dp[x][1], dp[x][0] - max(dp[y][0], dp[y][1]) + dp[y][0] + 1);
}
}
}
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(fa[i]);
vec[fa[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
int cur = 0;
get_loop(i);
dfs(u, 1);
cur = max(cur, dp[u][0]);
dfs(u, 0);
cur = max(cur, dp[u][1]);
ans += cur;
}
}
printf("%d", ans);
return 0;
}