Gragh---Algorithm ---最小樹形圖

定義:

定根的最小樹形圖,就是給有向帶權圖中指定一個特殊的點root,求一棵以root爲根的有向生成樹T,並且T中所有邊的總權值最小。


算法實現過程:(定根)

朱-劉算法的大概過程如下:參考:hqd_acm的專欄 +幻影閣

首先消除自環,顯然自環不在最小樹形圖中。然後判定是否存在最小樹形圖,以根爲起點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的距離定義如下:
<1> gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)而Vk到v的距離爲
<2> 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.

<1>的理解:先假設環上所有邊均選上,若下次選擇某一條邊進入該環,則可以斷開進入點與進入點的前驅之間的邊,即斷開F[進入點],因爲之前已經把現有的最小值加到集合A0裏面了,所以現在只要加上多出來的那一部分,也就是min{a[p,Vi]-f[Vi]},所以等效爲直接把a[p,node]賦值爲min{a[p,Vi]-f[Vi]},有點像增廣的感覺,每次找到delta。

附一張圖片加以理解:





4.cost加上Ao的權值和即爲最小樹形圖總權值。



複雜度:有固定根

找環O(V),收縮O(E),總複雜度O(VE)。


模板:(POJ 3164 裸的最小樹形圖)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include  <cmath>
#include <vector>
#include <cstring>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
struct point
{
    int x,y;
}P[105];

struct Edge
{
    int u,v;
    double w;
}edge[10005];

int pre[105],color[105],mark[105];
int cnt_edge;
double in[105];

void AddEdge(int u,int v,double w)
{
    edge[cnt_edge].u=u;
    edge[cnt_edge].v=v;
    edge[cnt_edge].w=w;
    cnt_edge++;
}

double Cal(int x1, int y1,int x2, int y2)
{
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

double zhuliu(int root,int n,int m)
{
    int i,j,u,v,cnt;
    double w;
    cnt=0;
    double res=0;

    while(1)
    {
        for(i=1;i<=n;i++)
            in[i]=inf;
        for(i=0;i<m;i++)
        {
            u=edge[i].u;
            v=edge[i].v;
            w=edge[i].w;
            if(in[v]>w&&u!=v)
            {
                in[v]=w;
                pre[v]=u;
            }
        }
        for(i=1;i<=n;i++)
        {
            if(i!=root&&in[i]==inf)
            return -1;
        }
        memset(mark,0,sizeof(mark));
        memset(color,0,sizeof(color));
        mark[root]=1;
        in[root]=0;
        cnt=0;

        for(i=1;i<=n;i++)
        {
            res+=in[i];
            v=i;
            while(mark[v]!=i&&color[v]==0&&v!=root)
            {
                mark[v]=i;
                v=pre[v];
            }
            if(v!=root&&color[v]==0)
            {
                cnt++;
                for(u=pre[v];u!=v;u=pre[u])
                    color[u]=cnt;
                color[v]=cnt;
            }
        }

        if(cnt==0)
            break;
        for(i=1;i<=n;i++)
            if(color[i]==0)
                color[i]=++cnt;
        for(i=0;i<m;i++)
        {
            v=edge[i].v;
            edge[i].u=color[edge[i].u];
            edge[i].v=color[edge[i].v];
            if(edge[i].u!=edge[i].v)
                edge[i].w-=in[v];
        }

        n=cnt;
        root=color[root];
    }
    return res;
}

int main()
{
    //freopen("in","r",stdin);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int i,j,f,t;
        cnt_edge=0;
        for(i=1;i<=n;i++)
            scanf("%d %d",&P[i].x,&P[i].y);
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&f,&t);
            if(f!=t)
            AddEdge(f,t,Cal(P[f].x,P[f].y,P[t].x,P[t].y));
        }
        double ans=zhuliu(1,n,cnt_edge);
        if(ans!=-1)
        printf("%.2f\n",ans);
        else printf("poor snoopy\n");
    }
    return 0;
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章