1. 程式人生 > >hdu 5723 Abandoned country 深搜回溯

hdu 5723 Abandoned country 深搜回溯

題目連結:hdu 5723

 

參考部落格:https://www.cnblogs.com/aiguona/p/7214707.html

題意:給出n個頂點,m條邊,讓你建一顆最小生成樹,問:最小生成樹的值是多少,任意兩點的期望長度是多少?

 

題解:

      任意兩點的期望是(權值)*(這條路被走過的次數)的總和   除以  總共的路徑數

  比如從A到B距離為2,從B到C距離為3。那麼從A到B走過2一次,從A到C走過2一次,走過3一次,從B到C走過3一次。

  所以期望為(2*2+3*2)/ 3 = 3.33。(也可以看成有三條路徑,1,A->B , B->C , A->C 其實B->A也算一種的,但我們可以不用考慮,因為求得是期望值,所以我們就按順著的方向來算路徑)。

  總共的路徑數 n*(n-1)/2 ,舉個例子給你,有4個頂點,那麼就有3個值,那麼首先有3條路徑,分別為 A->B B->C C->D (經過兩個頂點),

然後有2條路徑,分別為 A->C B->D(經過三個頂點) , 最後有1條路徑,為 A->D(經過4個頂點),很容易可以看出是個等差數列和(每次少一條路徑)。

     

    這裡不能求最短路,超時。

  應該用深搜回溯,找出這條路被走過過少次。這樣就可以求出期望了。

  這裡由於資料量比較大,不能用prim鄰接矩陣求最小生成樹(vector應該可以),可以用Kruskal求最小生成樹。

  坑點:注意最後求期望的時候總共的路徑數量n*(n-1)/2資料量比較大,應該用long long或者double儲存。

 

///程式碼參考部落格:https://www.cnblogs.com/aiguona/p/7214707.html
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<string.h>
using namespace std;
vector<pair<int,int> >  v[100010];

struct Edge
{
    int f,t,q;
};

int m,n;///n為村莊數,m為街道數
Edge s[1000010];///儲存圖
long long  ans;///存最後的每條路的總和
int pre[100010];///並查集的祖先陣列
int vis[100010];///標記陣列

bool cmp(Edge a,Edge b )///排序函式
{
    return a.q<b.q;
}

int Find(int x)///找祖先
{
    if(x!=pre[x])
    {
        pre[x]=Find(pre[x]);
    }
    return pre[x];
}

void Merge(int x,int y)///查是否相等
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
        pre[fx]=fy;
}

long long dfs(int x) ///dfs遞迴搜尋
{
    vis[x]=1;
    long long now=0,all=1;///now記錄當前節點直接連線的節點數量  all記錄此節點經過搜尋後所有的與此節點連線的節點數
    int h=v[x].size();
    for(int i=0; i<h; i++)
    {
        int b=v[x][i].first;
        if(!vis[b])
        {
            now=dfs(b);

            all+=now;
//            printf("b=%d,all=%lld,now=%lld\n",b,all,now);
            ///路徑數為 now*(n-now),解釋下:與x節點連線的後續節點數為now個,那麼與x連線的前置節點就有(n-now)個,
            ///所有的前置節點都可以和後續節點形成一條順著的路徑
            ///故為 now*(n-now)條路徑
            ans+=now*(n-now)*v[x][i].second;///ans記錄的是權值*經過的次數
        }
    }
    return all;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ans=0;
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&n,&m);
        if(m==0||n==0)
            {printf("0 0.00");continue;}

        for(int i=0;i<=n;i++)
            v[i].clear();
        for(int i=0; i<=n; i++)///並查集的祖先節點的初始化
        {
            pre[i]=i;
        }
        for(int j=0; j<m; j++)///輸入
        {
            scanf("%d%d%d",&s[j].f,&s[j].t,&s[j].q);
        }
        sort(s,s+m,cmp);///排序
        long long sum=0;///sum用來記錄最小生成樹的長度
        for(int j=0; j<m; j++)
        {
            int fx=Find(s[j].f);
            int fy=Find(s[j].t);
            if(fx!=fy)  ///如果祖先不相等,那麼加入到最小生成樹中
            {
                sum=sum+s[j].q;
                Merge(fx,fy);
                ///加入到動態陣列中準備做期望
                v[s[j].f].push_back(make_pair(s[j].t,s[j].q));
                v[s[j].t].push_back(make_pair(s[j].f,s[j]. q));
            }
        }
        dfs(1);///深搜回溯計算ans的值
        double y=1.0*n*(n-1)/2;
        printf("%lld %.2lf\n",sum,(double)ans/y);
    }
    return 0;
}