P10693题解

题意

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";

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值