本来寒假是安排的很充实的,放假了一回到家就(ノへ ̄、)
算法想想还是不能丢,虽然离ACM铜牌的水平还差好远,但是,不想留下遗憾,算法慢慢的要重新拾起来的,虽然以前水平也不咋地╯︿╰
原题为2017icpc亚洲区预赛乌鲁木齐网络赛F题island
https://nanti.jisuanke.com/t/16955
题目大意:一张有向图,问至少再添加几条有向边,才能使它成为强连通图。
主要写一下本题大致用到了哪些方面的知识,理一下思路。
强连通分量:在有向图中,如果一张图中的任意两点都可以相互到达,则称这个图为强连通图。图中任意两点之间可以相互到达的子图即为强连通分量。
tarjan算法:本质是通过一次dfs搜索有向图,用栈存储到达过的节点,每求出一个完整的强连通分量,就弹出对应的节点,计算强连通分量的个数(即之后缩点后点的个数)。
tarjan算法的详解可以看这位博主的博客:
http://blog.csdn.net/jeryjeryjery/article/details/52829142?locationNum=4
缩点:在每次求得一个强连通分量时,给当前强连通分量中的节点打上同一个标记,看成同一个节点,即缩点。
入度、出度:如有向边(u,v)不在同一个强连通分量中,则u所在强连通分量出度++,v所在强连通分量入度++。
计算入度为0和出度为0的强连通分量个数n,m
构成强连通图需要添加的最少边就是max(n,m),因为出度为0的点无法到达其他节点,入度为0的点无法被到达,则至少使得所有点入度和出度都大于0才能构成强连通图。
tarjan算法代码:
注意!这不是ac代码!(由于用的邻接矩阵存储边,最后内存超了)
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 10005
using namespace std;
int raid[maxn][maxn];
int low[maxn],dfn[maxn],sta[maxn],flag[maxn],suo[maxn],indegree[maxn],outdegree[maxn];
int idex=1,number=0,top=0,n,m;//idex时间戳,number强连通分量个数,top sta栈中位置
void tarjan(int u)
{
int i;
dfn[u]=low[u]=idex++;//dfn访问的次序,low当前节点能到达的最远的父亲节点的dfn(归入同一强连通分量)
sta[top++]=u;//入栈
flag[u]=1;//标记为已访问
for(int v=1;v<=n;v++)
{
if(raid[u][v])
{
if(!dfn[v])//如果v没有被访问过
{
tarjan(v);//递归调用
if(low[u]>low[v])//更新low
low[u]=low[v];
}
else//被访问过
{
if(dfn[v]<low[u]&&flag[v]==1)//v被访问过且v在栈中,防止被弹出的节点被误以为是父亲节点
{
low[u]=dfn[v];//指向父亲节点
}
}
}
}
//往后回溯,将栈中属于同一强连通分量的节点全部弹出
if(dfn[u]==low[u])
number++;
do
{
i=sta[top];
flag[i]=0;
suo[i]=number;//缩点,属于同一强连通分量的节点缩成一个点
top--;
}
while(i!=u);
}
int main()
{
int t,u,v;
scanf("%d",&t);
while(t--)
{
number=0;
idex=1;
top=0;
memset(raid,0,sizeof(raid));
memset(flag,0,sizeof(flag));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(sta,0,sizeof(sta));
memset(suo,0,sizeof(suo));
memset(indegree,0,sizeof(indegree));
memset(outdegree,0,sizeof(outdegree));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
raid[u][v]++;
}
int in=0,out=0,MAX=0;//入度和出度
tarjan(1);//若初始图不一定连通则需要循环调用tarjan函数
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(raid[i][j]!=0&&suo[i]!=suo[j])
{
indegree[suo[j]]++;
outdegree[suo[i]]++;
}
}
}
for(int i=1;i<=number;i++)
{
if(indegree[i]==0)
in++;
if(outdegree[i]==0)
out++;
}
MAX=max(in,out);
printf("%d\n",MAX);
}
return 0;
}