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