题目
n个城市,m条单向边(n,m<=5e3)
问至少需要加多少条单向边,
才能使得从源点s出发,所有点都可达
思路来源
https://www.cnblogs.com/shinianhuanniyijuhaojiubujian/p/9242441.html
题解
大概就是把图搞成全连通分量
原来求完强连通分量之后,是可以重建图然后dfs新图的……
这个东西用vector存更方便,毕竟能用两个不同的vector区分原图和现在的图
而链式前向星要么得head2[]、cnt2,要么求完tarjan之后把图初始化了
先求强连通分量,然后对s所在的强连通分量进行dfs
s可达的强连通分量显然对答案没有贡献,
其余的入度为0的强连通分量即为所求
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e4+5;
const int maxm=5e4+5;
int n,m,s;
int u[maxm],v[maxm];
int ans;//最后答案 出度为0点的个数
int head[maxn],cnt;
int low[maxn],dfn[maxn],num;//最早非负祖先时间戳 时间戳
int stack[maxn],top,now;//用数组模拟栈 栈顶 当前栈顶值
int par[maxn],tot;//染色 颜色数
bool in[maxn];//是否在栈中
bool vis[maxn];//该连通分量是否被访问过
int IN[maxn];//如果不在一个连通分量里 统计出度
int sum[maxn];//每个连通分量里的点的个数
struct edge
{
int to,next;
}e[maxm];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u)
{
low[u]=dfn[u]=++num;
in[u]=1;
stack[++top]=u;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])//环的第一个点
{
tot++;
do
{
now=stack[top--];
par[now]=tot;
sum[tot]++;
in[now]=0;
}while(now!=u);
}
}
void init1()
{
memset(head,0,sizeof head);
cnt=0;
}
void init2()
{
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(par,0,sizeof par);
memset(sum,0,sizeof sum);
memset(in,0,sizeof in);
memset(IN,0,sizeof IN);
ans=num=top=tot=0;
}
void dfs2(int u)
{
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(vis[v])continue;
dfs2(v);
}
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&s))
{
init1();//重新建图
init2();
for(int i=1;i<=m;++i)
{
scanf("%d%d",&u[i],&v[i]);
add(u[i],v[i]);
}
for(int i=1;i<=n;++i)
if(!dfn[i])dfs(i);
init1();//重新建图
for(int i=1;i<=m;++i)
{
if(par[u[i]]==par[v[i]])continue;
add(par[u[i]],par[v[i]]);
IN[par[v[i]]]++;//u->v
}
dfs2(par[s]);
for(int i=1;i<=tot;++i)
if(!IN[i]&&!vis[i])ans++;//从par[s]出发不可达 且入度为0的分量
printf("%d\n",ans);
}
return 0;
}