题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3072
题目描述:给出一个N个点(编号0~N-1),M个边的有向图,每个边有个权值cost,
求把关系传递给N个点的最小花费,强连通分量内部传递消息不需要花费。
1.如果不限制强连通分量内部传递消息不需要花费这个条件的话,这个题就是求
一个有向图的最小生成树,也叫最小树形图,朱刘算法套模板就可以了。
加了限制条件后,我们首先要做的就是对原来的图进行缩点操作。将一个强连通分量
缩成一个点,由于朱刘算法求最小树形图实质上也是进行一些缩点操作然后不断求
新图的最小入边权(算法详情自己百度),则我们先tarjan对图进行缩点,得出无环
有向图,由于图中已经无环了,欲想寻找一些边连接N个点,我们对于每个节点必定
有一个最小的入边(最小树形图的树根除外)。因此我们要找每个节点的最小入边权值
其中有点权值的最小入边无穷大(这些点都是入度为0的点,当然图中应该只会有一个
或0个,原因是题目并没有说图不联通怎样处理,所以应该不会有图不联通的情况)
所以把所有顶点的最小入边权小于无穷大的加和就是答案。如果学过朱刘算法求最小
树形图求最小生成树,这个题目就很容易有思路,就非常简单。
AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#define inf 1e12+2
using namespace std;
/*强连通缩点+不定根最小树形图*/
const int maxn = 50010;
const int maxm = 100010;
typedef long long LL;
int N,M; ///节点数和边数
struct Edge
{
int u;
int v;
int cost;
int nex;
}edge[maxm];
int edgeNumber; //图中边的数量
int head[maxn]; //链表头节点
//加边函数
void addEdge(int u,int v,int c)
{
edge[edgeNumber].u = u;
edge[edgeNumber].v = v;
edge[edgeNumber].cost = c;
edge[edgeNumber].nex = head[u];
head[u] = edgeNumber++;
}
int Stack[maxn]; ///栈
int inStack[maxn]; ///标记是否在栈中
int low[maxn]; ///类似并查集操作
int dfn[maxn]; ///存放访问的时间标记
int belong[maxn]; ///存放每个点所属的强连通分量的编号
int time,top,cnt; ///时间戳,栈顶指针,强连通分量个数
///找强连通分量缩点。
void tarjan(int u)
{
low[u] = dfn[u] = ++time;
Stack[++top] = u;
inStack[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].nex)
{
int v = edge[i].v;
if(!dfn[v]) ///没访问过
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(inStack[v]) ///访问过并在栈中
{
low[u] = min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])
{
cnt++;
int temp;
do
{
temp = Stack[top--];
inStack[temp] = 0;
belong[temp] = cnt;
}while(temp != u);
}
}
LL in[maxn]; ///最小入边权值
int main()
{
int u,v,c;
while(~scanf("%d%d",&N,&M))
{
edgeNumber = 0;
memset(head,-1,sizeof(head));
for(int i = 1; i <= M; i++)
{
scanf("%d%d%d",&u,&v,&c);
addEdge(u+1,v+1,c);
}
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(inStack,0,sizeof(inStack));
time = cnt = top = 0;
for(int i = 1; i <= N; i++)
{
if(!dfn[i])
tarjan(i);
}
if(cnt == 0) ///如果图是强连通图
printf("0\n");
else
{
///当前图已经被缩成cnt个点,求新图的每个点的最小入边权值(其实是求MST)。
for(int i = 1; i <= N; i++)
in[i] = inf;
for(int i = 0; i < edgeNumber; i++)
{
u = edge[i].u;
v = edge[i].v;
u = belong[u];
v = belong[v];
if(u != v) ///在一个强连通分量中,传递消息不需要花费。
{
if(edge[i].cost < in[v])
{
in[v] = edge[i].cost;
}
}
}
LL ans = 0;
for(int i = 1; i <= cnt; i++)
if(in[i]<inf)
ans += in[i];
printf("%lld\n",ans);
}
}
return 0;
}