题意
Shirost 作为树王国的庆典设计师,准备邀请 n 个嘉宾来参加本次庆典。庆典上一共准备了 2n 个座位,一个座位最多只能坐一个人且一个人恰好坐一个座位。Shirost 初步计划将第 i 个嘉宾安排在第 i 个座位上。但是总统调查了这 n 个嘉宾的意愿,第 i 个嘉宾的心仪座位为第 a_i 个座位。但除非能坐到心仪座位上,否则他们只愿意坐在原来的座位上。总统希望 Shirost 能够修改计划,使得尽可能多的嘉宾坐在他们的心仪座位上。
思路
首先我们观察到第 i 个人要坐在座位 i 或座位 a_i
那我们不妨连一条从 i 到 a_i 的边,来表示这个条件
观察样例:
样例连成的图中,只有两种结构:有向树和环
略经思考,发现在某些情况下,会出现内向基环树,我们把环也归至其中
对于有向树,答案显然为它最长链的长度(即深度 - 1)
对于基环树,答案显然为环上点的个数,因为若环外点坐到了心仪座位,则必然有环上点没有位置坐。而环上点是可以随便坐的。
那么如何求答案呢?
对于有向树,枚举每一个点dfs。但是可能会枚举到基环树,怎么办呢?
可见基环树上每个点都有出度,所以枚举 n + 1 ~ n * 2 ,在反图上进行 dfs ,因为这些点一定没有出度
对于基环树,做拓扑排序,最终入度仍不为 0 的点即为环上点
同时,为避免重复计算答案,应先进行有向树操作,并把枚举到的 <= n 的点打上标记
代码
int n;
int a[maxn << 1];
vector<int> z[maxn << 1];//正图,拓扑排序用
vector<int> fz[maxn << 1];//反图,dfs用
int in[maxn << 1];//入度
int ans;
bool f[maxn << 1];//标记
int cnt;
void dfs(int now,int le){
if(now <= n){
f[now] = 1;//打标记,避免重复计算答案
cnt = max(cnt,le);//树根肯定为 <= n 的点,所以不必更新 >= n 的点的深度
}
for(int i : fz[now]){
dfs(i,le + 1);
}
}
queue<int> q;
//以下为主函数部分
cin >> n;
for(int i = 1;i <= n;++ i){
cin >> a[i];
z[i].push_back(a[i]);
fz[a[i]].push_back(i);
++ in[a[i]];
}
for(int i = n + 1;i <= (n << 1);++ i){
cnt = 0;//深度计算
dfs(i,0);
ans += cnt;
}
for(int i = 1;i <= n;++ i){
if(!in[i] && !f[i]) q.push(i);
}
while(!q.empty()){
int op = q.front();
q.pop();
f[op] = 1;
for(int i : z[op]){
-- in[i];
if(!in[i]) q.push(i);
}
}//拓扑排序
for(int i = 1;i <= n;++ i) ans += !f[i];//环上点不可能有入度为0的
cout << ans << "\n";