CF698B Fix a Tree 题解

考虑把题目中的边设为有向边,ipii \rightarrow p_i。考虑每个连通块。

显然每个连通块是一个 xx 个点 xx 条边的图,且至多只有一个自环。于是这必然是一棵树,或者内向基环树。

首先确定最终树的根。如果存在任意一个 pi=ip_i=i,那么根就可以确定为 ii。有多个取任意一个即可。

如果没有,将某个基环树的环上某个点设为根,把 pup_u 改成 uu

接着考虑每个连通块。如果是树,把树根的 pp 改成最终树的根。否则搜出环,把环上任意一个点的 pp 改成根即可。显然每个连通块改一次,答案已经是最小的了。

实现上有一些细节。

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;

int n, a[N], na[N];
vector<int> G[N];
 
bool vis[N];
int rt;
bool isc = 0;
int cl;
int fp;

void dfs(int u, int fa)
{
	if (a[u] == u) fp = u;
	vis[u] = 1;
	for (auto &j : G[u])
	{
		if (j == fa) 
		{
			if ((na[u] == fa && na[fa] == u))
			{
				isc = 1;
				cl = j;
			}
			continue;
		}
		if (vis[j])
		{
			isc = 1;
			cl = j;
		}
		else dfs(j, u);
	}
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) 
	{
		cin >> a[i];
		na[i] = a[i];
		if (i == a[i])
		{
			rt = i;
			continue;
		}
		G[i].emplace_back(a[i]);
		G[a[i]].emplace_back(i);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		if (vis[i]) continue;
		isc = 0, cl = 0, fp = 0;
		dfs(i, 0);
		if (isc)
		{
			if (!rt) a[cl] = cl, rt = cl;
			else a[cl] = rt;
			ans++;
		}
		else
		{
			if (rt == fp) continue;
			a[fp] = rt;
			ans++;
		}
	}
	cout << ans << "\n";
	for (int i = 1; i <= n; i++) cout << a[i] << " ";
	return 0;
}
posted @   HappyBobb  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示