hdu 4009 最小树形图模版

最小树形图(摘自百度百科)

         最小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。最小树形图的第一个算法是1965年朱永津和刘振宏提出的复杂度为O(VE)的算法。
         判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的算法中不再考虑树形图不存在的情况。
在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。

        首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环缩成一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩的那个环的权和,就是原图中最小树形图的权。

        上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。

         如果实现得很聪明的话,可以达到找最小入边O(E),找环 O(V),收缩O(E),其中在找环O(V)这里需要一点技巧。这样每次收缩的复杂度是O(E),然后最多会收缩几次呢?由于我们一开始已经拿掉了所有的自环,我门可以知道每个环至少包含2个点,收缩成1个点之后,总点数减少了至少1。当整个图收缩到只有1个点的时候,最小树形图就不不用求了。所以我们最多只会进行V-1次的收缩,所以总得复杂度自然是O(VE)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。

/*

这题的实现完全参考这位大牛的博客,在这作为最小树形图的模版;

代码如下:

*/

#include<stdio.h>
#include<math.h>
#include<stdlib.h>

const int inf =1000000000;

int ne,nv;
struct Edge
{
    int from,to,c;
}edge[1100000];

void add(int a,int b,int c)
{
    edge[ne].from=a;
    edge[ne].to=b;
    edge[ne++].c=c;
}

int idx[1100],inc[1100],vis[1100],pre[1100];
int directTree(int root)
{
    int i,k,res=0,from,to,index;
    while(1)
    {
        for(i=0;i<nv;i++){ idx[i]=-1;vis[i]=-1;inc[i]=inf; } //初始化
        for(i=0;i<ne;i++) //找出每个点的最小入边
        {
            to=edge[i].to;
            from=edge[i].from;
            if(edge[i].c>=inc[to]||to==from)continue;
            pre[to]=from;
            inc[to]=edge[i].c;
        }
        inc[root]=0;pre[root]=root;
        for(i=0;i<nv;i++){ res+=inc[i]; if(inc[i]==inf) return -1; } //如果其中有一条没有入边,则最小树形图不存在,返回-1
        for(i=index=0;i<nv;i++) //找环,并把环缩成一点,新点的序号从0开始
        {
            if(vis[i]==-1)
            {
                to=i;
                while(vis[to]==-1){ vis[to]=i; to=pre[to];}
                if(vis[to]!=i||to==root) continue;
                for(k=pre[to];k!=to;k=pre[k]) idx[k]=index; //缩点
                idx[to]=index++;
            }
        }
        if(index==0)break;//如果图中无环,说明最小树形图已找到,退出
        for(i=0;i<nv;i++) if(idx[i]==-1) idx[i]=index++; //继续添加剩下的点
        for(i=0;i<ne;i++) //更新边
        {
            to=edge[i].to;
            edge[i].to=idx[edge[i].to];
            edge[i].from=idx[edge[i].from];
            edge[i].c-=inc[to];
        }
        nv=index; root=idx[root];//更新新点的数目,和root的序号
    }
    return res;
}

struct node
{
    int x,y,z;
}person[1100];


int dist(int a,int b){ return abs(person[a].x-person[b].x)+abs(person[a].y-person[b].y)+abs(person[a].z-person[b].z); }

int main()
{
    int i,j,n,X,Y,Z,p;
    while(scanf("%d%d%d%d",&nv,&X,&Y,&Z)!=EOF)
    {
        if(nv==0) break;
        for(i=1,ne=0;i<=nv;i++)
        {
            scanf("%d%d%d",&person[i].x,&person[i].y,&person[i].z);
            add(0,i,person[i].z*X);
        }
        for(i=1;i<=nv;i++)
        {
            scanf("%d",&n);
            while(n--)
            {
                scanf("%d",&p);
                if(p==i)continue;
                if(person[i].z>=person[p].z)
                    add(i,p,dist(i,p)*Y);
                else add(i,p,dist(i,p)*Y+Z);
            }
        }
        nv++;
        printf("%d\n",directTree(0));
    }
    return 0;
}



你可能感兴趣的:(hdu 4009 最小树形图模版)