题解 CF999E 【Reachability from the Capital】
\(\quad\)前置芝士:Tarjan缩点,建议做做模板题(P3387 【模板】缩点)
\[\text{关于题意}
\]
\(\quad\)给定一个有向图,求要使一个点 \(t\) 能够到达其他所有点还需加多少边?
\(\quad\)因为在一个强连通分量中,每个点都可以到达其他所有点,所以考虑缩点,将一个强连通分量中所有点看做一个点,再做其他操作。
\(\quad\)缩点之后图就变成了\(DAG\)(有向无环图),然后考虑每一个点的入度情况(缩点后),因为点 \(t\) 要能到达其他每一个点,所以除了点 \(t\) 所在的强连通分量之外,其他所有点的入度必须大于\(0\),通过找规律就可以发现答案就是缩点后入度为 \(0\) 且不是点 \(t\) 所在的强连通分量的点的数量。(因为即使t所在的强连通分量入度为0,也不需要连边)
\(\quad\)如果还不能理解就看代码吧,注释都在代码里。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<algorithm>
#define int long long
#define re register int
#define il inline
#define next ne
using namespace std;
il int read() //快速读入
{
int x=0,f=1;char ch=getchar();
while(isdigit(ch)==0&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch-'0'),ch=getchar();
return x*f;
}
const int N=5e3+5;
int n,m,dfn[N],low[N],c[N],f[N*2],s[N],l[N]
int next[N*2],go[N*2],head[N],tot,cnt,ans,t;
il void Add(int x,int y) //链式前向星存图,记录每条边的端点u,v,之后要计算每个点的入度
{
next[++tot]=head[x];
head[x]=tot;
f[tot]=x;
go[tot]=y;
}
stack<int>q; //个人喜欢用stack栈,不用数组模拟栈
il void Tarjan(int x) //Tarjan缩点板子
{
dfn[x]=low[x]=++cnt;
q.push(x);
for(re i=head[x];i;i=next[i])
{
int y=go[i];
if(!dfn[y])Tarjan(y),low[x]=min(low[x],low[y]);
else if(!c[y])low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
c[x]=++c[0];
s[c[0]]=1;
while(q.top()!=x)
{
c[q.top()]=c[0];
s[c[0]]++;
q.pop();
}
q.pop();
}
}
signed main()
{
n=read();m=read();t=read();
tot=cnt=0;
for(re i=1;i<=m;i++)
{
int x=read(),y=read();
Add(x,y);
}
for(re i=1;i<=n;i++)
if(!dfn[i])Tarjan(i);
if(c[0]==1){printf("0\n");return 0;} //如果只有一个强连通分量,不需要加边
for(re i=1;i<=m;i++)
{
int x=c[f[i]],y=c[go[i]];
if(x!=y)l[y]++; //记录每个点的入度(缩点后)
}
for(re i=1;i<=c[0];i++) //条件1:入度为0 条件2:不是t所在的强连通分量
if(!l[i]&&i!=c[t])ans++; //统计答案
printf("%lld\n",ans);
return 0;
}
\(\quad\)如果觉得我写得好的,不妨点个赞呗