题意: 输入n、m ,n表示人数和联系对数。联系不同人的花费不同,需要找到花费最少为多少。
思路:找到强连通分支(标记同一分支中的点),然后每个分支中最多只需要联系一个人即可,但若两个分支中有联系则只需联系其中一个花费小的,即将每个分支看做一个点,找分支间的出、入度关系。
#include<iostream>
#include<string.h>
#define size 1010
#define INF 1000000000
using namespace std;
struct E
{
int v,next;
}e1[2*size],e2[2*size];
int head1[size] ,head2[size];
int from[size*2] , to[size*2];
int n,m ,t ,scc ,top1 ,top2;
int f[size],state[size],state1[size];
int val[size],cost[size];
int rd[size] ;
void insert1(int u,int v)
{
e1[top1].v=v; e1[top1].next=head1[u]; head1[u]=top1++;
}
void insert2(int v, int u)
{
e2[top2].v=u; e2[top2].next=head2[v]; head2[v]=top2++;
}
void dfs(int u)
{
for(int i=head1[u];i!=-1;i=e1[i].next)
{
int v=e1[i].v;
if(state1[ v]==0)
{
state1[v]=1;
dfs(v);
}
}
f[++t]=u; //t表示时间,结束时间越晚越说明该点的连通性越强
//f[t]=u表示t时结束的点为u
}
void dfs_1(int u)
{
for(int i=head2[u]; i!=-1; i=e2[i].next)
{
int v=e2[i].v;
if(state[v]==0)
{
state[v]=scc; //cout<<u<<"----"<<e2[i].v<<endl;
dfs_1(v);
}
}
}
void kosaraju()
{
for(int i=0;i<=n;i++) state1[i]=state[i]=f[i]=0;
t=0;
for(int i=1;i<=n;i++)
{
if(state1[i]==0) { state1[i]=1; dfs(i);}
}
for(int i=0;i<=n;i++) state[i]=0;
scc=0;
for(int i=t;i>0;i--) //图的联系反向后,若仍可达,则称这两点是连通的(都用同一个scc标记)。
{ //cout<<"f[i]= "<<f[i]<<endl;
if(state[f[i]]==0) { state[f[i]]=++scc; dfs_1(f[i]);}
}//cout<<"scc= "<<scc<<endl;
for(int i=0;i<=scc;i++) {rd[i]=0; cost[i]=INF;}//找到每个连通分支中联系花费最小的一个人
for(int i=1;i<=n;i++)
{
if(cost[state[i]]>val[i]) cost[state[i]]=val[i];
}
for(int i=0;i<top1;i++) //将每个分支当做一个点,计算其入度,若入度为0,则说明必须联系
//其分支中的一个人,否则该分支可以通过其他分支中的人联系到。
{
if(state[from[i]]!=state[to[i]]) rd[state[to[i]]]++;
}
}
int main()
{
int u,v,sum,num;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=0;i<=m;i++) head1[i]=head2[i]=-1;
sum=num=0; top1=top2=0;
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
insert1(u,v); insert2(v,u);
from[i]=u; to[i]=v;
}
kosaraju();
for(int j=1;j<=scc;j++)
{// cout<<"rd["<<j<<"]= "<<rd[j]<<endl;
if(rd[j]==0) {sum+=cost[j] ; num++; }
}
printf("%d %d\n",num,sum);
}
return 0;
}