hdu 2121
View Code
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#define maxn 1005
const int inf =1000000000;
struct Edge
{
int u,v;
__int64 cost;
}E[maxn*maxn];
int nv,ne;
int pre[maxn],ID[maxn],vis[maxn];
__int64 In[maxn];
int ansi;
//最小树形图邻接表版本,三步走,找最小入弧,找有向环,缩环为点
__int64 Directed_MST(int root,int nv,int ne) {
__int64 ret = 0;
int i,u,v;
while(true) {
//1.找最小入边
for(i=0;i<nv;i++) In[i] = inf;
for(i=0;i<ne;i++){
u = E[i].u;
v = E[i].v;
if(E[i].cost < In[v] && u != v) {
pre[v] = u;
if(u==root)//记录是root从哪一条边到有效点的(这个点就是实际的起点)
ansi=i;
In[v] = E[i].cost;
}
}
for(i=0;i<nv;i++) {
if(i == root) continue;
if(In[i] == inf) return -1;//除了跟以外有点没有入边,则根无法到达它
}
//2.找环
int cntnode = 0;
memset(ID,-1,sizeof(ID));
memset(vis,-1,sizeof(vis));
In[root] = 0;
for(i=0;i<nv;i++) {//标记每个环
ret += In[i];
v = i;
while(vis[v] != i && ID[v] == -1 && v != root) {
vis[v] = i;
v = pre[v];
}
if(v != root && ID[v] == -1) {
for(u = pre[v] ; u != v ; u = pre[u]) {
ID[u] = cntnode;
}
ID[v] = cntnode ++;
}
}
if(cntnode == 0) break;//无环
for(i=0;i<nv;i++) if(ID[i] == -1) {
ID[i] = cntnode ++;
}
//3.缩点,重新标记
for(i=0;i<ne;i++) {
int v = E[i].v;
E[i].u = ID[E[i].u];
E[i].v = ID[E[i].v];
if(E[i].u != E[i].v) {
E[i].cost -= In[v];
}
}
nv = cntnode;
root = ID[root];
}
return ret;
}
int main()
{
int i,s,t;
__int64 sum,cost;
while(scanf("%d%d",&nv,&ne)!=EOF)
{
sum=0;
for(i=0;i<ne;i++)
{
scanf("%d%d%I64d",&s,&t,&cost);
s++;t++;
E[i].u=s;
E[i].v=t;
E[i].cost=cost;
sum+=cost;
}
sum++;
for(i=ne;i<ne+nv;i++)//按点序号从小到大枚举
{
E[i].u=0;
E[i].v=i-ne+1;
E[i].cost=sum;
}
__int64 ans=Directed_MST(0,nv+1,ne+nv);
if(ans==-1||ans-sum>=sum)
printf("impossible\n");
else
{
printf("%I64d %d\n",ans-sum,ansi-ne);
}
printf("\n");
}
return 0;
}
//本题需要输出最小树形图的最小根,其实不难,只要在添加从人工加的根到每个点边的权值时按点的从小到大枚举即可
//所以在找最小入弧时,如果某条弧的起点是虚拟根,则这条弧的终点就为最小树形图的根
//因为如果有多解,那么原图中肯定存在一个环,因此虚拟根连到这个环上的任意一点都是最小树形图
//然而,我们最先碰到的环上的点是序号较小的点,碰到后就立即进行缩环为点的操作,因此,只要保存下这个点的序号就好了
//不知道这样理解对不对