基于深度优先搜索,用于寻找有向图中的连通块。
主要代码如下:
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){//还未访问过这个节点
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){//这个节点在栈中
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){//找到一个强联通分量
++cnt;
while(x!=sta[top]){
vis[sta[top]]=0;
qlt[sta[top]]=cnt;//染色,标记这个节点属于第几号强联通分量
siz[cnt]++;//记录这一个连通块的节点数
top--;
}
vis[x]=0;//还要将这个节点弹出来
qlt[x]=cnt;
siz[cnt]++;
top--;
}
}
如果是要遍历整张图找到所有的连通块,通常还要加这句
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
因为图中不一定全部都是联通的。
每一个点只访问了一次,所以tarjan的时间复杂度是O(n)的。
通常还要用到tarjan缩点。缩点的时候将全部边遍历一次,
如果两个节点不属于同一个连通块,那么就连一条边,为了避免这种情况:
a->c b->c a,b属于同一个连通块。
可以用并查集处理一下,避免重复将两个相同的连通块连边。(重复连边好像也并没有什么影响)
USACO 2003 Fall:
popular cow
这道题首先要知道:对于一个有向无环图,出度为零的点,从其他任意一个点都可以到达这个点。
所以我们只需要将原图用tarjan缩点变成一个有向无环图,找到出度为零的点,输出那个连通块的大小就行了。
#include<bits/stdc++.h>
#define cg c=getchar()
#define N 10020
#define M 50050
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
int n,m,aa,bb;
struct node{
int v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int idx;
int dfn[N];
int low[N];
int sta[N];
int vis[N];
int top;
int cnt;
int qlt[N];
int siz[N];
int cd[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
vis[sta[top]]=0;
qlt[sta[top]]=cnt;
siz[cnt]++;
top--;
}
vis[x]=0;
qlt[x]=cnt;
siz[cnt]++;
top--;
}
}
int main(){
read(n);read(m);
for(int i=1;i<=m;i++){
read(aa);read(bb);
add(aa,bb);
}
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=e[j].nxt){
int v=e[j].v;
if(qlt[i]!=qlt[v]){
cd[qlt[i]]++;//因为不用重建一张图,只需要记录出度就行了
}
}
}
//因为重构的图也有可能存在两个连通块之间没有边连接,它们的出度也为零,所以要考虑这种情况
int ans=0;
int id;
for(int i=1;i<=cnt;i++){
if(cd[i]==0){
ans++;
id=i;
}
}
if(ans==1){//只能有一个出度为零的连通块
print(siz[id]);
}
else print(0);
}
IOI 1996 网络协议
这道题有两个问题,第一个问题还是比较明显,就是缩点后看有几个入度为0的点,每一个入度为零的点都必须放消息。
第二问就相当于将缩点后的图变成一个连通块。每一个入度为零的边都要增加一条入度,每一个出度为零的边都要增加一条出度。最后取较大值。
有个小细节,如果缩点后只有一个点,那么就不用加边了,所以要特判一下。
code:
#include<bits/stdc++.h>
#define N 120
#define M 13000
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
struct node{
int u,v,nxt;
}e[M],e2[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].u=a;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int n;
int aa;
int idx;
int top;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int qlt[N];
int cnt;
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
qlt[sta[top]]=cnt;
vis[sta[top]]=0;
top--;
}
qlt[x]=cnt;
vis[x]=0;
top--;
}
}
int rd[N];
int cd[N];
int ans;
int main(){
// freopen("protocols10.in","r",stdin);
read(n);
for(int i=1;i<=n;i++){
while(true){
read(aa);
if(aa==0) break;
add(i,aa);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=tot;i++){
int x=e[i].u;
int y=e[i].v;
if(qlt[x]!=qlt[y]) {
rd[qlt[y]]++;
cd[qlt[x]]++;
}
}
int ans1=0;
int ans2=0;
for(int i=1;i<=cnt;i++){
if(rd[i]==0){
ans1++;
ans++;
}
if(cd[i]==0){
ans2++;
}
}
print(ans);putchar('\n');
if(cnt==1) print(0);//特判 只有一个连通块,不需要加边。
else print(max(ans1,ans2));
}
消息的传递:
跟上一题的第一问一样。。。只不过输入变成了邻接矩阵。
code:
#include<bits/stdc++.h>
#define cg c=getchar()
#define N 1200
#define M N*(N-1)/2
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
struct node{
int u,v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].u=a;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int n;
int aa;
/*--------------------------*/
int idx;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int top;
int cnt;
int qlt[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
sta[++top]=x;
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
qlt[sta[top]]=cnt;
vis[sta[top]]=0;
top--;
}
qlt[x]=cnt;
vis[x]=0;
top--;
}
}
int rd[N];
int main(){
read(n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
read(aa);
if(aa==1){
add(i,j);
}
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=tot;i++){
int x=e[i].u;
int y=e[i].v;
if(qlt[x]!=qlt[y]){
rd[qlt[y]]++;
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
if(rd[i]==0){
ans++;
}
}
print(ans);
}
原文:https://www.cnblogs.com/doublety/p/11519454.html