题解 P2835 【刻录光盘】
去他的并查集,这题根本不是并查集……来自并查集只有28分的蒟蒻的抱怨
我们把每个人向他愿意分享的人连一条单向边,然后自己画图瞎搞一下,很容易发现:
只有每个入度为0的点需要光盘!
所以我们只要在读入时记录每个点的入度,最后找有多少入度为0的点即可。
有了这个思想,你就……只有81分。别问我怎么知道的!
这个思想本身是没错的,但你有没有想过……图中出现环的情况呢?所有点入度都不为0。
这就十分尴尬。
我们再考虑特判一下。画个图你会发现:
只要图中有环,与这个环连通的所有点都只需要一个光盘。
这里的“连通”指有边连接,无论这条边指向哪里。
这就好办了,我们每次选择一个未被标记过的点(初始每个点都未被标记),把与这个点连通的所有点标记上(BFS),一直到所有点都被标记为止。选了多少次,则代表图中有多少个连通块,最后统计每个连通块的结果即可。
当然,要特别注意的是,同一个连通块中,如果有入度为0的点,则需要的光盘数等于入度为0的点的数量,否则为1。
上代码(附有简短注释)
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<int>g[202]; //我习惯用vector模拟邻接表
queue<int>q;
int n,ans=0,to,f[202]={0},zero;
//zero存储每个块中入度0的点,f数组记录入度
bool b[202]={0};
int main()
{
int now,to,l,i,j;
cin>>n;
for(i=1;i<=n;i++)
{
scanf("%d",&to);
while(to!=0)
{
g[i].push_back(to);
g[to].push_back(i);//一定要连双向边!!!
f[to]++;
scanf("%d",&to);
}
}
for(i=1;i<=n;i++) //BFS
if(!b[i])
{
zero=0;
while(!q.empty()) q.pop();
b[i]=true;
q.push(i);
while(!q.empty())
{
now=q.front();
q.pop();
if(!f[now]) zero++;
l=g[now].size();
for(j=0;j<l;j++)
{
to=g[now][j];
if(!b[to])
{
b[to]=true;
q.push(to);
}
}
}
ans+=max(1,zero);
}
cout<<ans;
return 0;
}