SSL 2344 刻录光盘

SSL 2344 刻录光盘

原题链接


题目大意

简单来说,就是给出几个有向边,求连通块的数目。


算法选择

200的数据,O(n^3)的算法也轻松过,所以用Floyd或其他更快的算法都行


Code:这里给出Floyd、Kosaraju和Tarjan算法

Floyd

思路

先用Floyd求出这些员工的互相连通情况,然后用并查集把i的祖先设定为能够给i光盘的点上,最后求出有几个点的父节点仍然是他自己,就能求出来了;

#include<iostream>
#include<cstdio>
using namespace std;
int n,father[210],ans=0;
bool a[210][210];
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1,t;i<=n;i++)while(cin>>t and t!=0) a[i][t]=true;
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				a[i][j]=a[i][j] or (a[i][k] and a[k][j]);//Floyd大法
	for(int i=1;i<=n;i++) father[i]=i;//初值,每个结点最初的祖先就是他们自己
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(a[i][j]) father[j]=father[i];//如果可以抵达,则终点的祖先就等于起点的祖先
	for(int i=1;i<=n;i++)if(father[i]==i) ans++;//有几个结点的祖先仍然是自己,就有几个连通块
	cout<<ans;
	return 0;
}

Kosaraju

思路

Kosaraju模板;

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,ans,f[210];
bool a[210][210],vis[210];
void d1(int x){
	vis[x]=1;
	for(int i=1;i<=n;i++)
		if(a[x][i] and !vis[i]) d1(i);
	f[++m]=x;
}
void d2(int x){
    vis[x]=1;
    for (int i=1;i<=n;i++)
    if (a[x][i] and !vis[i]) d2(i);
}//d2相比d1少了一行f[++m]=x,因为留着的话会引发错乱;
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1,t;i<=n;i++)while(cin>>t,t) a[i][t]=true;
	for(int i=1;i<=n;i++){if(!vis[i]) d1(i);}memset(vis,0,sizeof(vis));//算法第一步求退出顺序
	for(int i=m;i>0;i--){if(!vis[f[i]]) ans++;d2(f[i]);}//算法第二步累加
	cout<<ans;//输出
	return 0;
}

tarjan

#include<iostream>
#include<cstdio>
#define sco 100010
using namespace std;
struct node{int u,v,next;}e[sco];
int st[sco],top=0;
int n,k,cnt=0,cum,dfn[sco],low[sco],vis[sco],ls[sco],cor[sco],rd[sco],ans;//ans是累加答案的变量
//n表示人数,e是邻接表,k、ls是邻接表的辅助变量,cnt、dfn、low、vis、cor,rd都是tarjan的辅助变量;
//其中,cnt表示第几次访问,dfn[i]表示i这个点是第几次访问的,low[u]=min(low[u],low[v]);
//cor是染色变量,把同一个强连通分量里的数字都染上同样的数字即cum;rd[i]储存i节点的入度;
void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	st[++top]=x;vis[x]=1;
	int t=ls[x];
	while(t){
		if(!dfn[e[t].v]){
			tarjan(e[t].v);
			low[x]=min(low[x],low[e[t].v]);
		}
		else if(vis[e[t].v]){
			low[x]=min(low[x],low[e[t].v]);
		}
		t=e[t].next;
	}
	if(dfn[x]==low[x]){
		vis[x]=0;
		cor[x]=++cum;
		do{
			vis[st[top]]=0;
			cor[st[top]]=cum;
		}while(st[top--]!=x);
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1,t;i<=n;i++){
		while(cin>>t,t){
			e[++k].u=i;e[k].v=t;e[k].next=ls[e[k].u];ls[e[k].u]=k;
		}
	}
	for(int i=1;i<=n;i++)if(dfn[i]==0) tarjan(i);
	for(int i=1;i<=k;i++)if(cor[e[i].u]!=cor[e[i].v]) rd[cor[e[i].v]]++;
	for(int i=1;i<=cum;i++)if(!rd[i]) ans++;
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值