题1:P2002 消息扩散
题目描述
有n个城市,中间有单向道路连接,消息会沿着道路扩散,现在给出n个城市及其之间的道路,问至少需要在几个城市发布消息才能让这所有n个城市都得到消息。
输入输出格式
输入格式:
第一行两个整数n,m表示n个城市,m条单向道路。
以下m行,每行两个整数b,e表示有一条从b到e的道路,道路可以重复或存在自环。
输出格式:
一行一个整数,表示至少要在几个城市中发布消息。
输入样例
5 4
1 2
2 1
2 3
5 1
输出样例
2
//P2002 消息扩散
#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
#define MAXN 100005
#define MAXM 500005
using namespace std;
int n,m;
int head[MAXN]={0};//链式向前星储存
int dfn[MAXN]={0};//时间戳数组,dfs[x]指x是第几个被搜索点点
int low[MAXN]={0};//和该点强连通点最早被搜索点
bool in[MAXN]={false};//是否在栈中
int edge_num=0;
int sum=0;
int color=0;//同一个强连通分量染成同样颜色
int belong[MAXN]={0};//一个点属于哪个强连通分量
int in_degree[MAXN]={0};//强连通分量的入度
int ans=0;
stack<int> st;
struct edge
{
int next;
int to;
int from;
}eg[MAXM];
void add(int a,int b)
{
eg[edge_num++].from=a;
eg[edge_num].next=head[a];
head[a]=edge_num;
eg[edge_num].to=b;
}
void scanff()
{
scanf("%d%d",&n,&m);
for(int i=1,a,b;i<=m;i++)
{
scanf("%d%d",&a,&b);
if(a!=b)//排除自环
add(a,b);
}
}
void tarjan(int x)
{
sum++;
dfn[x]=low[x]=sum;//初始化
st.push(x);
in[x]=true;
for(int i=head[x];i;i=eg[i].next)
{
//遍历每一个连通的点
int t=eg[i].to;
//未搜索过,就深搜
if(!dfn[t])
{
tarjan(t);
low[x]=min(low[x],low[t]);
}
//搜索过但未确定属于哪个强连通分量(在栈中)
else if(in[t])
low[x]=min(low[x],low[t]);
}
if(low[x]==dfn[x])
{
color++;
int k=-1;
//所有比x后入栈且不属于其他强连通分量的点与x同属一个
//确定属于哪个强连通分量,依次出栈
while(k!=x)
{
k=st.top();
st.pop();
belong[k]=color;
in[k]=false;
}
}
}
void work()
{
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
//一条边横跨两个强连通分量,入度增加
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=eg[j].next)
if(belong[i]!=belong[eg[j].to])
in_degree[belong[eg[j].to]]++;
for(int i=1;i<=color;i++)
if(!in_degree[i])
ans++;
printf("%d\n",ans);
}
int main()
{
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
scanff();
work();
return 0;
}
题2:P2341 受欢迎的牛
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你算出有多少头奶牛可以当明星。
输入输出格式
输入格式:
第一行:两个用空格分开的整数:N和M
第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B
输出格式:
第一行:单独一个整数,表示明星奶牛的数量
输入输出样例
输入样例
3 3
1 2
2 1
2 3
输出样例
1
分析:
用tarjan将每个强连通分量坍缩成一个点后形成一个有向无环图(若成环那此环又可以成为一个强连通分量)。
若一个点是明星,则所有点都通向它,它不通向任何点(否则成环)。因此,其出度为0。一个连通子图中只能有这么一个“明星点”(否则非连通)。若此图为非连通图,就没有明星,直接输出0;否则输出“明星点”的成员数。
//
#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
#define MAXN 100005
#define MAXM 500005
using namespace std;
int n,m;
int head[MAXN]={0};
int edge_num=0;
stack<int> st;
int dfn[MAXN]={0};
int low[MAXN]={0};
bool in[MAXN]={false};
int belong[MAXN]={0};
int num[MAXN]={0};//记录每个强连通分量中点点个数
int sum=0;
int color=0;
//和上题基本一样
int out_degree[MAXN]={0};
struct edge
{
int next;
int to;
}eg[MAXM];
void add(int a,int b)
{
eg[++edge_num].next=head[a];
head[a]=edge_num;
eg[edge_num].to=b;
}
void scanff()
{
scanf("%d%d",&n,&m);
for(int i=1,a,b;i<=m;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
}
}
void tarjan(int x)
{
dfn[x]=low[x]=++sum;
st.push(x);
in[x]=true;
for(int i=head[x];i;i=eg[i].next)
{
int t=eg[i].to;
if(!dfn[t])
{
tarjan(t);
low[x]=min(low[x],low[t]);
}
else if(in[t])
low[x]=min(low[x],low[t]);
}
if(dfn[x]==low[x])
{
color++;
int k=-1;
while(k!=x)
{
k=st.top();
st.pop();
belong[k]=color;
num[color]++;//记录每个强连通分量点的个数
in[k]=false;
}
}
}
void work()
{
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=eg[j].next)
{
int t=eg[j].to;
if(belong[i]!=belong[t])
out_degree[belong[i]]++;
}
}
int s=0;
for(int i=1;i<=color;i++)
{
if(!out_degree[i])
{
//如果已有“明星点”后又找到明星点,说明非连通
if(s)
{
printf("0\n");
return;
}
s=i;
}
}
printf("%d\n",num[s]);
}
int main()
{
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
scanff();
work();
return 0;
}