题面
每一个奶牛都喜欢自己,除此以外还有一些喜欢的对象,用边表示。
并且奶牛的喜欢符合传递关系,即A喜欢B,B喜欢C则A喜欢C
被全体奶牛喜欢的奶牛称为明星
给出N头奶牛和M个喜欢关系,求明星数量
1<=N<=10000,1<=M<=50000
分析
如果整个图只是一个强连通分量,那么显然所有点间都可相互到达(喜欢都能传递到),所以图内所有点都是明星。
考虑有多个强连通分量的情况,可以把每个强连通分量先缩点。
像这种情况,因为有F→C的边,所以分量2中的点都能到达分量1,分量1的出度为0,也就可以做到让分量1内的ABC成为明星
而如果删去F→C的边,分量1和2的出度都为0,就不可能做到某个牛成为明星,因为另一个分量的牛都不喜欢它
经过思考,最终的情况其实就是:当存在大于等于2个分量出度为0时,就不存在明星(如两个出度0的分量中取一个牛,另一个分量中的牛都不喜欢它,不满足所有牛都喜欢)
1个分量出度为0时,其余分量的喜欢可以传递给它,所以这个分量内所有牛都是明星
0个分量出度0,空图。。。就不存在了。
这是求完强连通分量之后的缩点问题,关于求强连通分量,我用的是tarjan的板子。
tarjan法求强连通分量的算法其实和求割点差不多,都用到了pre,low函数,一样的dfs。
标记分量的方法就是找到dfs中第一个进入某个分量的元素,这个元素的特点是low[u]=pre[u],在dfs过程中用一个栈存下还没有分配连通块的元素,一旦遇到了low[u]=pre[u],就连续出栈到u,这一次出栈的元素都属于同一个强连通分量。
代码
#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
vector<int> G[50004];
int dfs_clock = 0, scc_cnt = 0;//时间戳,强连通分量组编号
int sccno[10004];//表示每个点属于哪个强连通块
int pre[10004];//dfs序
int low[10004];//low[u]表示u及其后带通过反向边最多能返回到哪
stack<int> s;//存当前的节点
int out[10004];//出度统计,序号是强连通分量的编号
void dfs(int u)
{
pre[u] = low[u] = ++dfs_clock;
s.push(u);
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];//下一个
if (!pre[v])//未到过v
{
dfs(v);
low[u] = min(low[u], low[v]);
}
else if (!sccno[v])//有可能是新的反向边
{
low[u] = min(low[u], pre[v]);
}
}
if (low[u] == pre[u])//是第一次发现这个连通分量的点
{
scc_cnt++;
while (true)
{
int x = s.top();s.pop();
sccno[x] = scc_cnt;
if (x == u)break;
}
}
}
void find_scc(int n)
{
for (int i = 1; i <= n; i++)
{
if (!pre[i])dfs(i);
}
}
int main()
{
ios::sync_with_stdio(false);
int n, m,u,v;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> u >> v;
G[u].push_back(v);
}
find_scc(n);//开始寻找强连通分量的函数
//sccno已经处理完,1~scc_cnt
//开始处理每个块的出度问题
for (int i = 1; i <= n; i++)
{
for (int j = 0;j < G[i].size();j++)//(i,G[i][j])是一条边
{
if (sccno[i] != sccno[G[i][j]])out[sccno[i]]++;//不是同一个块,则i的出度++
}
}
int ans = 0;//此时记录out为0的块的个数
for (int i = 1; i <= scc_cnt; i++)
{
if (out[i] == 0)ans++;
}
if (ans > 1)cout << 0;//有2个及以上出度0的块,不存在明星
else {
ans = 0;
for (int i = 1; i <= n; i++)
{
if (out[sccno[i]] == 0)ans++;//输出出度0的块内所有点
}
cout << ans;
}
return 0;
}
求强连通分量部分参考了《算法竞赛入门经典-训练指南》的板子