poj 3164

     这题如果不是分类那里写着刘朱算法的话,可不好做。

     先说说刘朱算法,这里是转载的内容:

 

有固定根的最小树形图求法O(VE):

首先消除自环,显然自环不在最小树形图中。然后判定是否存在最小树形图,以根为起点DFS一遍即可。

之后进行以下步骤。

设cost为最小树形图总权值。
0.置cost=0。
1.求最短弧集合Ao (一条弧就是一条有向边)

除源点外,为所有其他节点Vi,找到一条以Vi为终点的边,把它加入到集合Ao中。

(加边的方法:所有点到Vi的边中权值最小的边即为该加入的边,记prev[vi]为该边的起点,mincost[vi]为该边的权值)

2.检查Ao中的边是否会形成有向圈,有则到步骤3,无则到步骤4。

(判断方法:利用prev数组,枚举为检查过的点作为搜索的起点,做类似DFS的操作)

3.将有向环缩成一个点。
假设环中的点有(Vk1,Vk2,… ,Vki)总共i个,用缩成的点叫Vk替代,则在压缩后的图中,其他所有不在环中点v到Vk的距离定义如下:
gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)而Vk到v的距离为
gh[Vk][v]=min { gh[Vkj][v] }              (1<=j<=i)
同时注意更新prev[v]的值,即if(prev[v]==Vkj) prev[v]=Vk
另外cost=cost+mincost[Vkj] (1<=j<=i)

到步骤1.

4.cost加上Ao的权值和即为最小树形图总权值。

如要输出最小树形图较烦,没实现过。

找环O(V),收缩O(E),总复杂度O(VE)。

那幅对我有莫大帮助的流程图如下,

对于不固定根的最小树形图,wy教主有一巧妙方法。摘录如下:

新加一个点,和每个点连权相同的边,这个权大于原图所有边权的和,这样这个图固定跟的最小树形图和原图不固定跟的最小树形图就是对应的了。

       接下来是代码了,主要难点是消除环和找环内的点的合适的入边和出边,参考了大牛的代码,但还是做了好几个钟。。。

   

  1. #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    using namespace std;
    #define MIN(x,y) ((x)>(y)?(y):(x))
    const double inf=999999999.9;
    const int M=200;
  2. struct point
    {
     double x,y;
    }points[M];
    double map[M][M],ans;
    bool vis[M],vis1[M];
    int pre[M];
    int e,t,f,cnt;
    int n,m;
  3. void init()
    {
     int i,j;
     for(i=0;i<=n+2;i++)
     {
      for(j=0;j<=n+2;j++)
       map[i][j]=inf;
     }
     cnt=0;
    }
  4. void dfs(int s)
    {
     vis[s]=1;
     for(int i=1;i<=n;i++)
      if(map[s][i]<inf && !vis[i])
      {
       cnt++;
       dfs(i);
      }
    }
  5. void solve()
    {
     int i,j,k;
     memset(vis,0,sizeof(vis));
     while(1)
     {
      for(i=2;i<=n;i++)
       if(!vis[i])  //遍历各个结点,记录其前继结点
       {
        k=0;
        for(j=1;j<=n;j++)
         if(j!=i && !vis[j] && (!k || map[k][i]>map[j][i]))
          k=j;
        pre[i]=k;
       }
      for(i=2;i<=n;i++)//找自环
       if(!vis[i])
       {
        memset(vis1,0,sizeof(vis1));
        j=i;
        while(j!=1 && !vis1[j])
        {
         vis1[j]=1;
         j=pre[j];
        }
        if(j==1) continue;//不存在环
        i=j;  //取出环上一点
        ans+=map[pre[i]][i];
        for(j=pre[i];j!=i;j=pre[j])
        {
         ans+=map[pre[j]][j];  //记录环中的各个边的权值
         vis[j]=1;
        }
        for(j=1;j<=n;j++)
         if(!vis[j] && map[j][i]<inf) //到目标点的各个入边权值减去in[u]
         {
          map[j][i]-=map[pre[i]][i];
         }
        for(j=pre[i];j!=i;j=pre[j])
  6. //更新环上各个点的出边的权值,使得环外各个点的入边有最小权值
  7. //同时更新环上点入边的权值,使之最小
        {
         for(k=1;k<=n;k++)
          if(!vis[k])
          {
           map[i][k]=MIN(map[i][k],map[j][k]);
           if(map[k][j]<inf)
            map[k][i]=MIN(map[k][i],map[k][j]-map[pre[j]][j]);
          }
        }
        break;
       }
      if(i>n)
      {
       for(i=2;i<=n;i++)
        if(!vis[i])
         ans+=map[pre[i]][i];
       break;
      }
     }
    }
  8. int main()
    {
     while(scanf("%d%d",&n,&m)==2)
     {
      if(!n) break;
      init();
      int i,j,k;
      int a,b;
      for(i=1;i<=n;i++)
       scanf("%lf%lf",&points[i].x,&points[i].y);
      while(m--)
      {
       scanf("%d%d",&a,&b);
       double temp=sqrt((points[a].x-points[b].x)*(points[a].x-points[b].x)+(points[a].y-points[b].y)*(points[a].y-points[b].y));
       map[a][b]=MIN(map[a][b],temp);
      }
      memset(vis,0,sizeof(vis));
      cnt=1;
      dfs(1);
      if(cnt<n)
       printf("poor snoopy/n");
      else
      {
       ans=0;
       solve();
       printf("%.2lf/n",ans);
      }
     }
     return 0;
    }
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值