Problem Description:
To see a World in a Grain of Sand
And a Heaven in a Wild Flower,
Hold Infinity in the palm of your hand
And Eternity in an hour.
—— William Blake
听说lcy帮大家预定了新马泰7日游,Wiskey真是高兴的夜不能寐啊,他想着得快点把这消息告诉大家,虽然他手上有所有人的联系方式,但是一个一个联系过去实在太耗时间和电话费了。他知道其他人也有一些别人的联系方式,这样他可以通知其他人,再让其他人帮忙通知一下别人。你能帮Wiskey计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?
Input :多组测试数组,以EOF结束。
第一行两个整数N和M(1<=N<=1000, 1<=M<=2000),表示人数和联系对数。
接下一行有N个整数,表示Wiskey联系第i个人的电话费用。
接着有M行,每行有两个整数X,Y,表示X能联系到Y,但是不表示Y也能联系X。
Output: 输出最小联系人数和最小花费。
每个CASE输出答案一行。
Sample Input
12 16
2 2 2 2 2 2 2 2 2 2 2 2
1 3
3 2
2 1
3 4
2 4
3 5
5 4
4 6
6 4
7 4
7 12
7 8
8 7
8 9
10 9
11 10
Sample Output
3 6
强连通分量内的点是可以互相通知的,因此一个强连通分量只需通知一个点就够了,又要花费最小,就通知花费最小的那个点,然后让那个点通知其他点(即缩点)。这样花费仍然不是最小的,我们不需要通知所有强连通分量,只需要通知那些没有其他强连通分量可以通知到的强连通分量,即最高强连通分量。
最高强连通分量的定义:如果一个强连通分量内部的弧都是向外的,即对于任意一点u不属于该强连通分量,不存在点v,v属于该强连通分量,使得v是u的后代(即u->v的边)。换句话说,最高强连通分量没有外部的点指向这个强连通分量,如果把最高强连通分量缩成一个点,那么是没有其他点指向这个点的。
把所有强连通分量缩成一个点,显然最高强连通分量是可以指向其他强连通分量的。我们只要通知所有最高强连通分量,那么整个图就都可以通知了(此即最小点基:所有最高强连通分量的内部的一个点构成的点集,通过该点集可以访问整个图任意一个点)。如果我们通知的是最高强连通分量内花费最低的那个点,那么这题就完美解决了(此即最小权点基:满足最小基性质,且若每个点有点权,该点集点权之和最小)。
算法步骤:
第一步先找出所有的强连通分量。(用Tarjan算法)
第二步找出所有的最高强连通分量。(思考怎么找)
第三步从每一个最高强连通分量里找出一个点权最小的点。
Tarjan算法可以找出所有的强连通分量,并且给每个点标上该点属于哪个强连通分量。我们可以在这个过程加入一个维护:维护每个强连通分量的代表点的最小点权
void Tarjan(int s){
vis[s]=1;
stack[++top]=s;
dfn[s]=low[s]=++lay;
for(int i=head[s];i+1;i=edg[i].nxt){
if(!vis[edg[i].v]) Tarjan(edg[i].v);
if(vis[edg[i].v]==1) low[s]=min(low[s],low[edg[i].v]);
}
if(low[s]==dfn[s]){
++sec;
do{
bel[stack[top]]=sec;
vis[stack[top]]=2;
cmin[sec]=min(cmin[sec],value[stack[top]]); //维护每个强连通分量的最小点权
}while(stack[top--]!=s);
}
}
Tarjan算法处理完后,我们就已经维护好了每一个强连通分量得最小点权,接下来 只需要找出最高强连通分量的个数,把它们的点权加起来 就是答案了。可以遍历一遍边集,维护一个入度,然后找出所有入度为0的强连通分量,贴出代码:
//最小权点基
#include<iostream>
using namespace std;
#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f3f
const int maxm = 2010;
const int maxn = 1010;
struct EDG{
int u,v,nxt;
};
int n,m;
EDG edg[maxm];
int head[maxn],vis[maxn],stack[maxn],bel[maxn];
int dfn[maxn],low[maxn];
int lay,cnt,top,sec;
int value[maxn],cmin[maxn];
int info[maxn];
void init(){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
top=0;cnt=0;sec=0;lay=0;
memset(bel,0,sizeof(bel));
memset(value,-1,sizeof(value));
memset(cmin,INF,sizeof(cmin));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(info,0,sizeof(info));
}
void addedg(int x,int y){
edg[cnt].u=x;
edg[cnt].v=y;
edg[cnt].nxt=head[x];
head[x]=cnt++;
}
void Tarjan(int s){
vis[s]=1;
stack[++top]=s;
dfn[s]=low[s]=++lay;
for(int i=head[s];i+1;i=edg[i].nxt){
if(!vis[edg[i].v]) Tarjan(edg[i].v);
if(vis[edg[i].v]==1) low[s]=min(low[s],low[edg[i].v]);
}
if(low[s]==dfn[s]){
++sec;
do{
bel[stack[top]]=sec;
vis[stack[top]]=2;
cmin[sec]=min(cmin[sec],value[stack[top]]);
}while(stack[top--]!=s);
}
}
int x,y,ans1,ans2;
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
init();
for(int i=1;i<=n;i++) scanf("%d",&value[i]);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
addedg(x,y);
}
for(int i=1;i<=n;i++){
if(!vis[i]){
Tarjan(i);
}
}
ans1=ans2=0;
for(int i=0;i<cnt;i++){
if(bel[edg[i].u]!=bel[edg[i].v]) info[bel[edg[i].v]]++;
}
for(int i=1;i<=sec;i++){
if(!info[i]){
ans1++;
ans2+=cmin[i];
}
}
printf("%d %d\n",ans1,ans2);
}
}