最小樹形圖(朱劉算法)


前天做題遇到hdu2121 時一點思路都沒有,於是往后面一道題去了,結果做了hdu2122(求最小生成樹)之后受到啟發,hdu2121分明就是一道求有向圖的生成樹,也就是最小樹形圖。。。。

於是搜了很久,啃了很久,終於……

主要在這下面三個博客上學習的:

很贊的圖,http://hi.baidu.com/bin183/blog/item/45c37950ece4475f1138c273.html

很牛逼的理論http://www.zlinkin.com/?p=63

很好的模板+題目http://www.notonlysuccess.com/index.php/mst/#more-315

pku3164 Command Network

定根,求最小樹形圖,模板題

View Code
Source Code

Problem: 3164  User: nanke_ 
Memory: 276K  Time: 125MS 
Language: C++  Result: Accepted 

Source Code 
#include<iostream>
#include<algorithm>
#include<string>
#include<math.h>
using namespace std;
const int N = 100+10;
const int M = 10000+10;
struct Point
{
    double x,y;
}p[N];
struct edge
{
    int u,v;
    double cost;
    edge(){}
    edge(int u,int v,double c):u(u),v(v),cost(c){}
}e[M];
int pre[N],hash1[N],vis[N];
double In[N];
inline double dist(Point& a,Point& b)
{
    return sqrt(double((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)));
}
double Directed_MST(int root,int n,int m)
{
    double ret=0;
    while(true)
    {
        for(int i=0;i<n;i++)
            In[i]=INT_MAX;
        for(int i=0;i<m;i++)//找最小入邊
        {
            int u=e[i].u;
            int v=e[i].v;
            if(e[i].cost<In[v] && u!=v){
                pre[v]=u;
                In[v]=e[i].cost;
            }
        }
        for(int i=0;i<n;i++)
        {
            if(i==root)
                continue;
            if(In[i]==INT_MAX)
                return -1;
        }
        int cntnode=0;
        memset(hash1,-1,sizeof(hash1));
        memset(vis,-1,sizeof(vis));
        In[root]=0;
        for(int i=0;i<n;i++)//找環
        {
            ret+=In[i];
            int v=i;
            while(vis[v]!=i && hash1[v]==-1 && v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root && hash1[v]==-1)
            {
                for(int u=pre[v];u!=v;u=pre[u])
                    hash1[u]=cntnode;
                hash1[v]=cntnode++;
            }
        }
        if(cntnode==0)
            break;
        for(int i=0;i<n;i++)
            if(hash1[i]==-1)
                hash1[i]=cntnode++;
        for(int i=0;i<m;i++)//重標記
        {
            int v=e[i].v;
            e[i].u=hash1[e[i].u];
            e[i].v=hash1[e[i].v];
            if(e[i].u!=e[i].v)
                e[i].cost-=In[v];
        }
        n=cntnode;
        root=hash1[root];
    }
    return ret;
}
int main()
{
    int a,b;
    int n,m;
    while(scanf("%d %d",&n,&m)==2)
    {
        for(int i=0;i<n;i++)
            scanf("%lf %lf",&p[i].x,&p[i].y);
        int mm=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d %d",&a,&b);
            if(a==b)continue;
            a--,b--;
            e[mm++]=edge(a,b,dist(p[a],p[b]));
        }
        double ans=Directed_MST(0,n,mm);
        if(ans==-1)
            puts("poor snoopy");
        else printf("%.2f\n",ans);
    }
    return 0;
}

hdu2121  Ice_cream’s world II

不定根的題目,“只要虛擬一個根連所有的點的權為邊權總和+1,最后的結果減去(邊權+1)即可”。另外對於求最小根標號,只要搜索被選中的虛邊就可以判斷了。,與虛根相連的點即為原本的實根!

如果選擇了倆條或倆條以上的與虛根相連的邊,則不存在對應的最小樹形圖

View Code
#include<iostream>
#include<climits>
#include<math.h>
using namespace std;
const int N = 1000+10;
const int M = N*N;
struct edge
{
    int u,v;
    int cost;
}e[M];
int pre[N],id[N],visit[N];//id用來標記圈的
int in[N];//入弧最小的
int minRoot;
int Directed_MST(int root,int n,int m)//n表示點數,m表示邊數,root表示根
{
    int u,v,i;
    int ret=0;
    while(true)
    {
        for(i=0;i<n;i++)
            in[i]=INT_MAX;
        for(i=0;i<m;i++)
        {
            u=e[i].u;
            v=e[i].v;
            if(e[i].cost<in[v]&&u!=v)
            {
                pre[v]=u;//找出每個點的最小入弧
                if(u==root)
                    minRoot=i;
                in[v]=e[i].cost;
            }
        }
        for(i=0;i<n;i++)
        {
            if(i==root)
                continue;
            if(in[i]==INT_MAX){//除根外有個節點無入弧,就返回false
                return -1;
            }
        }
        in[root]=0;
        int cnt=0;
        memset(id,-1,sizeof(id));
        memset(visit,-1,sizeof(visit));
        for(i=0;i<n;i++)
        {
            ret+=in[i];//進行縮圈
            v=i;
            while(visit[v]!=i&&id[v]==-1&&v!=root)
            {
                visit[v]=i;
                v=pre[v];
            }
            if(v!=root&&id[v]==-1)
            {
                for(u=pre[v];u!=v;u=pre[u])
                    id[u]=cnt;
                id[v]=cnt++;
            }
        }
        if(cnt==0)
            break;
        for(i=0;i<n;i++)
        {
            if(id[i]==-1)
                id[i]=cnt++;
        }
        for(i=0;i<m;i++)
        {
            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];
        }
        n=cnt;
        root=id[root];
    }
    return ret;
}
int main()
{
    int n,m,m1;
    int T,c;
    int r=0;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int i,a,b;
        r=0;
        m1=m;
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            e[i].u=a;
            e[i].v=b;
            e[i].cost=c;
            r+=c;
        }
        r++;
        for(i=0;i<n;i++)
        {
            e[m].u=n;
            e[m].v=i;
            e[m].cost=r;
            m++;
        }
        int ans=Directed_MST(n,n+1,m);
        minRoot-=m1;//最小根對應的標號為i-m1
        if(ans==-1||ans>=2*r)
            puts("impossible");
        else
            printf("%d %d\n",ans-r,minRoot);
        puts("");
    }
    return 0;
}

hdu4009 Transfer water

這題目略有區別,卻更好理解。。

對於每一個household 有倆個選擇

1)自己挖井,花費 h*X

2)通過其他願意供水的household連一條水管,根據高度有倆種情況的花費

接下來,對應上面倆種選擇,我們先假定一個虛根,那么對應第一種選擇,我們可以通過虛根“供水”,虛根往每一個節點連一條邊,權值為h*x

對於第二種選擇,則將其與願意供水的節點連一條邊,權值為對應 的花費

這樣,就變成了定根的最小樹形圖,而且,一定有解,大不了就是每一個household都自己挖井

View Code
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int N = 1000+10;
const int M = N*N;
struct Point
{
    int x,y,z;
}p[N];
struct edge
{
    int u,v,cost;
    edge(){}
    edge(int u,int v,int cost):u(u),v(v),cost(cost){}
}e[M];
int pre[N],id[N],in[N],vis[N];
int X,Y,Z;
inline int Directed_MST(int root,int n,int m)//n表示點數,m表示邊數,root表示根
{
    int u,v,i;
    int ret=0;
    while(true)
    {
        for(i=0;i<n;i++)
            in[i]=INT_MAX;
        for(i=0;i<m;i++)
        {
            u=e[i].u;
            v=e[i].v;
            if(e[i].cost<in[v]&&u!=v)
            {
                pre[v]=u;//找出每個點的最小入弧
                in[v]=e[i].cost;
            }
        }
        for(i=0;i<n;i++)
        {
            if(i==root)
                continue;
            if(in[i]==INT_MAX){//除根外有個節點無入弧,就返回false
                return -1;
            }
        }
        in[root]=0;
        int cnt=0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        for(i=0;i<n;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]=cnt;
                id[v]=cnt++;
            }
        }
        if(cnt==0)
            break;
        for(i=0;i<n;i++)
        {
            if(id[i]==-1)
                id[i]=cnt++;
        }
        for(i=0;i<m;i++)
        {
            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];
        }
        n=cnt;
        root=id[root];
    }
    return ret;
}
inline int get_cost(Point& a,Point& b)
{
    int dis=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
    if(a.z>=b.z)
        return dis*Y;
    return dis*Y+Z;
}
int main()
{
    int n,m,k,a;
    while(scanf("%d %d %d %d",&n,&X,&Y,&Z)==4 && (n||X||Y||Z))
    {
        m=0;
        for(int i=1;i<=n;i++)
            scanf("%d %d %d",&p[i].x,&p[i].y,&p[i].z);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&k);
            while(k--)
            {
                scanf("%d",&a);
                e[m++]=edge(i,a,get_cost(p[i],p[a]));
            }
        }
        for(int i=1;i<=n;i++)
            e[m++]=edge(0,i,p[i].z*X);
        printf("%d\n",Directed_MST(0,n+1,m));
    }
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM