luoojP1381这不是Floyd

1381: 这不是Floyd

时间限制: 1 Sec   内存限制: 128 MB
提交: 68   解决: 28
[ 提交][ 状态][ 讨论版]

题目描述

当一个有向图给出,我们可以通过著名的Floyd算法方便的求解出其传递闭包。 
但如果你给一个图G=(V,E),您能不能给一个的最小的边集合代替原边集,使得新的图与原图各个顶点的传递性不变。 

输入

有多组测试数据: 
第一行,包含一个整数Num,表示测试数据的个数。(1<=Num<=100) 
每组测试数据,第一行一个整数N(1<=N<=200)。 
接下来N行N列的一个0,1矩阵,表示相应点对之间的连接关系。第i行第j列,若值为1,则表示有一条从i到j的有向边。否则就没有。 

输出

每行输出一个整数。输出至少需要多少条边,就能与原图的传递性一致。

样例输入

4
1
1
2
1 0
0 1
2
1 1
1 1
3
1 1 1
0 1 1
0 0 1

样例输出

0
0
2
2

提示

来源

分析
这是一道裸题,加上floyd之后很灵活,就是先有强连通分量缩点,然后在弗洛伊德跑出可以删的边,注意初始化的问题。还有就是这里不能用sccno的数组判断是否是在栈里,因为可能又自边。
代码如下:
#include<bits/stdc++.h>
#define N 305
using namespace std;
int ans,n,scc_cnt,g[N][N],nscc[N];
int tot,l[N],r[N],p[N][N];
stack <int> s;
int dfs_clock,low[N],dfn[N],sccno[N],instack[N];
void tarjan(int u){
	dfn[u]=low[u]=++dfs_clock;
	s.push(u);
	instack[u]=1;
	for(int v=1;v<=n;v++)
		if(g[u][v]){
			if(!dfn[v]){
				tarjan(v);
				low[u]=min(low[u],low[v]);
			}else
			if(instack[v]) low[u]=min(low[u],dfn[v]); //这里不是常规的Tarjan而是用那个instack来表示是否有重边
		}
	if(low[u]==dfn[u]){
		scc_cnt++;
		for(;;){
			int v=s.top();
			s.pop();
			sccno[v]=scc_cnt;
			instack[v]=0;//清空
			nscc[scc_cnt]++;
			if(u==v)break;
		}
		if(nscc[scc_cnt]>1)ans+=nscc[scc_cnt];//如果缩点数大于1则说明边数为点数就加上点数如果是一个点自然是没有边
	}
}//标准的强联通分量加缩点
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		tot=scc_cnt=dfs_clock=ans=0;
		memset(p,0,sizeof(p));
		memset(g,0,sizeof(g));
		memset(dfn,0,sizeof(dfn));
		memset(low,0,sizeof(low));
		memset(nscc,0,sizeof(nscc));//因为是多组数据所以要初始化
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%d",&g[i][j]);
		for(int i=1;i<=n;i++)
			if(!dfn[i])tarjan(i);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(g[i][j]&&sccno[i]!=sccno[j]){
					tot++;
					l[tot]=sccno[i];
					r[tot]=sccno[j];
					p[sccno[i]][sccno[j]]++;
				}//存储缩点后的连通边数
		for(int k=1;k<=scc_cnt;k++)
			for(int i=1;i<=scc_cnt;i++)
				for(int j=1;j<=scc_cnt;j++)
					p[i][j]+=p[i][k]*p[k][j];//Floyd跑出所有的路径数
		ans+=tot;//加上缩点数,即最坏的答案
		for(int i=1;i<=tot;i++)
			if(p[l[i]][r[i]]>1){
				p[l[i]][r[i]]--;
				ans--;
			}//去除可以删的边
		printf("%d\n",ans);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值