【个人专题一】强连通——Poj_2186

思路1代码:
#include<stdio.h>
#include<vector>
//#include<memory.h>

using namespace std;

//存储变量
int x,y;
vector<int> f[10000];//存储正向图
vector<int> n[10000];//存储反向图

bool sign[10000];
//辅助变量
int i,g,h,sum,r;

void init()
{
	//初始化
	memset(sign,0,sizeof(sign));
	sum=0;
	//清空相连表
	for(i=0;i<x;i++)
	{
		f[i].clear();
		n[i].clear();
	}

    //存储相连表
	for(i=0;i<y;i++)
	{
		scanf("%d%d",&g,&h);
		f[g-1].push_back(h-1);
		n[h-1].push_back(g-1);
	}
	r=0;
}

void search(int k,vector<int> l[10000])
{
	vector<int>::iterator t;
	sign[k]=true;
	sum++;
	for(t=l[k].begin();t!=l[k].end();t++)
	{
		if(!sign[*t])
			search(*t,l);
	}
}

int doit()
{
	//在反向图里搜索源_强连通子图(即正向图的出度为0的强连通子图)
	for(i=0;i<x;i++)
	{
		if(!sign[i])
		{
			r=i;
			search(r,n);
		}
	}


	//如果以r作为起点在反向图中深搜能遍历全图,则r就是受全部人欢迎的牛,否则不存在受全部人欢迎的牛,返回0
	sum=0;
	memset(sign,0,sizeof(sign));
	search(r,n);
	if(sum<x) return 0;

	//如果存在r,则与r在同一个强连通子图的牛都是受全部人欢迎的牛,计算其数目(因为已知r在反向图中能找到所有,
	//即正向图中都能找到r,则正向图中以r为根节点进行深搜,能碰到的点都是和r在同一个强连通子图中
	sum=0;
	memset(sign,0,sizeof(sign));
	search(r,f);
	return sum;
}

int main()
{
	while(scanf("%d%d",&x,&y)!=EOF)
	{
		init();
		printf("%d\n",doit());
	}
	return 0;
}

思路2代码:
#include<iostream>
using namespace std;
#define DOTMAX 10001
#define EDGEMAX 50001
struct node
{
    int t;
    node *next;
}dotset[EDGEMAX*3];//直接申请好全部空间,然后哪个空间放哪个节点临时生成即可

int count=0;//每一个case后,count置为0
node *Newnode()//调用上面申请好的全部空间里的一个节点作为存储地点
{
    node *p;
    p=&dotset[count];
    count++;
    return p;
}

void Addnode(node hash[],int a,int b)//在相连表上插入一个节点
{
    node *p=&hash[a];
    node *q=Newnode();
    q->t=b;
    q->next=p->next;
    p->next=q;
}

node hash[DOTMAX];
node nhash[DOTMAX];
node New[DOTMAX];

int gcc;
int order[DOTMAX];
int num;
int id[DOTMAX];
int visit[DOTMAX];
int gccnum[DOTMAX];

void init(node hash[],int n)//初始化相连表
{
    count=0;
    int i;
    for(i=1;i<=n;i++)
    {
		
        hash[i].t=-1;
        hash[i].next=NULL;
    }
}

int n,m;
void dfs(int u)//正向图中深搜
{
	
    visit[u]=1;
    node *p;
    int v;
    for(p=hash[u].next;p!=NULL;p=p->next)
    {
        v=p->t;
        if(!visit[v])
        {
            dfs(v);
        }
    }
    num++;
    order[num]=u;//用于反向搜索的顺序,可避免排序从而提高效率
}

void ndfs(int u)//反向图中搜索
{
    visit[u]=1;
    id[u]=gcc;
    node *p;
    int v;
    for(p=nhash[u].next;p!=NULL;p=p->next)
    {
        v=p->t;
        if(!visit[v])
        {
            ndfs(v);
        }
    }
}


int main()
{
    int a,b,i;
    while(scanf("%d%d",&n,&m)!=EOF)
    {	
		//初始化三个相连表
        init(hash,n);
        init(nhash,n);
        init(New,n);
        for(i=1;i<=m;i++)
        {
			
            scanf("%d%d",&a,&b);
            Addnode(hash,a,b);
            Addnode(nhash,b,a);
        }
        memset(visit,0,sizeof(visit));
        num=0;
		//正向深搜,找到根节点
        for(i=1;i<=n;i++)
        {
            if(!visit[i])
                dfs(i);
        }
        memset(visit,0,sizeof(visit));
        gcc=0;
		//反向深搜
        for(i=num;i>=1;i--)
        {
            if(!visit[order[i]])
            {
                gcc++;
                ndfs(order[i]);
            }
        }
        for(i=1;i<=n;i++)
        {
            node *p;
            for(p=hash[i].next;p!=NULL;p=p->next)
            {
                if(id[i]!=id[p->t])//如果不是属于同一个强连通
                {
					
                    Addnode(New,id[i],id[p->t]);//构建新图相连表
                }
            }
        }
        int cnt=0;
        memset(gccnum,0,sizeof(gccnum));
        for(i=1;i<=n;i++)
            gccnum[id[i]]++;//各连通子图中的顶点个数
        int mark=0;
        for(i=1;i<=gcc;i++)//搜索gcc个子图(视为顶点)
        {
            if(New[i].next==NULL)//出度为0
            {
                cnt++;
                mark=i;
            }
        }
		
        if(cnt==1)	//如果出度为0的只有一个,该连通子图的个数即所求个数
            printf("%d\n",gccnum[mark]);
        else//无结果
            printf("%d\n",0);
    }
	return 0;
}
 
自己照思路2敲了一遍。。(344Ms)
#include<stdio.h>
#include<string.h>
struct Node{
	int sub;
	Node *next;
}hash[10001],nhash[10001],newhash[10001];

bool IsVisit[10001];

int N,M,order[10001],sum,setNum[10001],num[10001],setnum;//sum代表已经访问结束的顶点个数


void init(int n)//初始化
{
	int i;
	for(i=1;i<=n;i++)
		hash[i].next=nhash[i].next=newhash[i].next=NULL;
	memset(IsVisit,false,sizeof(IsVisit));
	memset(num,0,sizeof(num));
	sum=0;
	setnum=0;//强连通子图的标记
}

void AddNode(struct Node thash[],int tstt,int tend)//添加节点
{
	struct Node *p=new struct Node;
	p->sub=tend;
	p->next=thash[tstt].next;
	thash[tstt].next=p;
}

void dfs(int x)
{
	IsVisit[x]=true;
	struct Node *p;
	for(p=hash[x].next;p!=NULL;p=p->next)
	{
		if(!IsVisit[p->sub])
			dfs(p->sub);
	}
	order[++sum]=x;
}

void ndfs(int x)
{
	IsVisit[x]=true;
	setNum[x]=setnum;
	num[setnum]++;
	Node *p;
	for(p=nhash[x].next;p!=NULL;p=p->next)
	{
		if(!IsVisit[p->sub])
			ndfs(p->sub);
	}
}

int main()
{
	while(scanf("%d%d",&N,&M)!=EOF)
	{
		init(N);
		int i,stt,end;
		for(i=1;i<=M;i++)
		{
			scanf("%d%d",&stt,&end);

			AddNode(hash,stt,end);
			AddNode(nhash,end,stt);
		}

		for(i=1;i<=N;i++)
		{
			if(!IsVisit[i])
			{
				dfs(i);
			}
		}

		memset(IsVisit,false,sizeof(IsVisit));

		for(i=N;i>0;i--)
		{
			if(!IsVisit[order[i]])
			{
				setnum++;
				ndfs(order[i]);
			}
		}

		for(i=1;i<=N;i++)
		{
			Node *p;
			for(p=hash[i].next;p!=NULL;p=p->next)//将强连通子图缩为一点,构建新图
			{
				if(setNum[i]!=setNum[p->sub])
					AddNode(newhash,setNum[i],setNum[p->sub]);
			}
		}

		int hole=0,k;

		for(i=1;i<=setnum;i++)
		{
			if(newhash[i].next==NULL)
			{
				hole++;
				k=i;
			}
		}
		if(hole==1) printf("%d\n",num[k]);
		else printf("0\n");
	}
	return 0;
}

 题目大意:在一群牛中,寻找受所有牛欢迎的牛的数量

思路1:

1、在反向图中深搜,找到“根节点”(最后结束的是“根节点”),然后在反向图中深搜“根节点”,如果能遍历全部顶点,则该点并是反向图中的根节点,也就是受所有牛欢迎的牛,转到2。否则输出0,结束。

2、 和该根节点的牛在同一强连通子图的牛也是受全部牛欢迎的牛。这里只要在正向图中深搜根节点,记录搜索到的数目,就是我们要的输出。

 

思路2:

1、在正向图中深搜,形成森林,记录各个节点的结束时间。

2、按时间从大到小在反向图中深搜,深搜未中断前,给每个被搜顶点记录相同且唯一的数字,代表属于同一个强连通子图(以上为Kosaraju算法)

3、利用强连通子图构建新图(此时必定无环,有环的已经属于同一个强连通子图而缩为新图的一个顶点

4、如果新图中出度为0的节点有多个,则返回0。否则返回出度为0的那个强连通子图中顶点的个数。

(思路2的代码来自http://www.cppblog.com/abilitytao/archive/2009/09/26/97302.html,不过我加了注释,需要吸收的是相连表用vector建立十分耗时,此外思路2的找强连通子图的做法在深搜完后不用排序即可立即进行反向搜索(可能已经很通用,只是我刚学,自己的思路是要排序,呵呵)

 



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值