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的数组判断是否是在栈里,因为可能又自边。
这是一道裸题,加上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;
}