P1543 [POI2004] SZP 題解

Atserckcn發表於2024-08-20

P1543 [POI2004] SZP 題解

傳送門。

題目簡述

\(n\) 個人,每個人都會監視另一個人,要求選出儘可能多的同學,使得選出的每一名同學都必定會被監視到。且選出的同學不可再監視其他人。

思路簡述

因為任意一個人只能被另一個人管,那麼就想到,如果沒人管的同學就不能被選(不被監視)。

若某個人有多個人監視,且監視他的有至少一個專門監視(監視他的那個人沒人監視)則他不得不去。

那麼再看看如果出現環咋辦。

不如畫個圖理解。

上圖即為一個環:\(1\) 監視 \(2\)\(2\) 監視 \(3\)\(3\) 監視 \(1\)

那麼不妨列舉一下。

如果派出 \(1\),則 \(3\) 可以監視到,而 \(2\) 也可以監視到 \(3\),完美符合題意。

但是,若取出了 \(1\)\(2\)\(2\) 則會沒人監視(本來監視他的 \(1\) 號走了)。

所以可以得出結論:若遇到環,設 \(s\) 為環的節點個數,則取出的個數為 \(\lfloor\frac{s}{2}\rfloor\)

程式碼實現

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,to[N],ans,cnt_ring,in[N];
bool gone[N]/*被選中了嗎*/,vis_ring[N]/*遍歷過了嗎*/;
queue<int > q;//注意:這裡的q可不是說進佇列了就得被選中
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&to[i]);
		++in[to[i]];//入度+1
	}
	for(int i=1;i<=n;i++)
		if(!in[i])//拓撲排序老闆子
			q.push(i);
	while(!q.empty())
	{
		int t=q.front();q.pop();
		vis_ring[t]=true;//判斷是否遍歷過
		if(gone[t])//他走了,他監視的同學就看看情況
		{
			if((--in[to[t]])==0)
				q.push(to[t]);
		}
		else//他沒走,他監視的孩子可就遭老罪嘍
		{
			if(!gone[to[t]])//孩子沒走
			{
				++ans;
				gone[to[t]]=true;//給我走
				q.push(to[t]);
			}
		}
	}
	for(int i=1;i<=n;i++)//開始判環
	{
		if(!vis_ring[i]&&in[i])
		{
			cnt_ring=0;//作用如其名
			for(int j=i;!vis_ring[j];j=to[j])
			{
				++cnt_ring;
				vis_ring[j]=true;
			}
			ans+=cnt_ring/2;//剛說的,不過C++自動向下取整
		}
	}
	printf("%d\n",ans);
	return 0;
}