1. 程式人生 > >「日常訓練」 Genghis Khan the Conqueror(HDU-4126)

「日常訓練」 Genghis Khan the Conqueror(HDU-4126)

題意

給定\(n\)個點和\(m\)條無向邊(\(n\le 3000\)),需要將這\(n\)個點連通。但是有\(Q\)次(\(Q\le 10^4\))等概率的破壞,每次破壞會把\(m\)條邊中的某條邊的權值增大某個值,求\(Q\)次破壞每次將\(n\)個點連通的代價的期望?(全題的資料範圍在int內可以過)

分析

這題是真的牛逼,我看了七八個部落格都沒看太明白,大多數人都沒講在點子上,但是還是有幾篇部落格不錯的,參考如下:
參考A:https://blog.csdn.net/u014664226/article/details/49333081
參考B:https://blog.csdn.net/Anxdada/article/details/81086041


參考C:https://blog.csdn.net/ramay7/article/details/52236040 (這個是最好的,強烈推薦)
參考D:https://blog.csdn.net/gatevin/article/details/47042021 (有一些“實質上”的東西)

接下來說說我綜合這些參考後自己對這題的理解與做法。

求期望的意思是,將每次破壞後的最小生成樹的代價累加除以\(Q\)。然後我們仔細思考一下這個破壞。首先,如果更改發生在不是最小生成樹上的邊上,那麼答案是不需要改變的。重點是改變發生在這棵生成樹上的邊中的情況下。此時這棵最小生成樹會分成兩棵樹。顯然地,新圖的最小生成樹一定包含這兩棵樹上的所有邊。問題於是轉化為原來的最小生成樹被切成兩棵樹之後,如何選擇權值最小的一條邊將兩棵樹連通。
這裡因此運用了樹形dp。這裡比較精彩:
我們記\(dp[i][j]\)

是切斷\((i,j)\)邊後,i與j兩個所在點的集合間的最短距離。但是我們不去直接這麼搜尋,而是去搜索i所在樹的樹根與j所在子樹的每一個點的最短距離。於是我們將每個點當作樹根進行DFS,在更新(搜尋)時,我們用j所在子樹所有點同root的直接距離更新掉dp陣列,並有意歸避掉\((i,j)\)邊。可以想見,當第\(i\)輪更新完成,dp中一定儲存了第1個到第\(i\)個root到他們相關點的最短距離。那麼對每個點都dp過後,最後dp數組裡面一定儲存的就是我們要的東西了。

最後對每個查詢做修正就可以了,具體見程式碼。真實樹形dp+最小生成樹好題,就是做的頭疼,哈哈。

程式碼

/* ACM Code written by Sam X or his teammates.
 * Filename: hdu4126.cpp
 * Date: 2018-11-18
 */

#include <bits/stdc++.h>

#define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end()

#define QUICKIO                  \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)

using namespace std;
using pi=pair<int,int>;
using repType=int;
using ll=long long;
using ld=long double;
using ull=unsigned long long;

int n,m;

struct Edge
{
    int u,v,w;
    Edge() {}
    Edge(int _u,int _v, int _w):
        u(_u), v(_v), w(_w) {}
    bool operator < (const Edge& rhs) const
    {
        if(w==rhs.w)
        {
            return u<rhs.u;
        }
        else return w<rhs.w;
    }
};
const int MAXN=3005;
vector<Edge> edges;
int mat[MAXN][MAXN];
int used[MAXN][MAXN];

int edges_ord[18000005];
int pa[MAXN];
vector<Edge> nedges;
vector<int> nG[MAXN];
inline void nadd_edge(int u,int v,int w)
{
    nedges.PB(u,v,w);
    nG[u].PB(int(nedges.size())-1);
}
int find_pa(int x)
{
    return pa[x]==x?x:pa[x]=find_pa(pa[x]);
}
inline void union_pa(int x,int y)
{
    int fx=find_pa(x),
        fy=find_pa(y);
    if(fx!=fy) pa[fx]=fy;
}
inline int kruskal()
{
    int ret=0;
    iota(pa,pa+n,0);
    sort(ALL(edges));
    rep(i,0,edges.size()-1)
    {
        int u=edges[i].u,
            v=edges[i].v,
            w=edges[i].w;
        if(find_pa(u)!=find_pa(v))
        {
            union_pa(u,v);
            ret+=w;
            used[u][v]=used[v][u]=w;
            nadd_edge(u,v,w);
            nadd_edge(v,u,w);
        }
    }
    return ret;
}

int dp[MAXN][MAXN];
int dfs(int root, int now, int pre)
{
    //cout<<root<<" "<<now<<" "<<pre<<endl;
    int ans=INF;
    rep(i,0,int(nG[now].size())-1)
    {
        int& v=nedges[nG[now][i]].v;

        if(v!=pre)
        {
            int tmp=dfs(root,v,now);
            ans=min(ans,tmp);
            dp[now][v]=dp[v][now]=min(dp[now][v], tmp);
        }
    }
    if(root!=pre && pre!=-1)
    {
        ans=min(ans, mat[now][root]);
    }
    return ans;
}

inline void init()
{
    edges.clear();
    nedges.clear();
    rep(i,0,n-1) nG[i].clear();
    MS(dp,0x3f);
    MS(used,-1);
    MS(mat,0x3f);
}

int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        if(!n && !m) break;
        init();
        rep(i,0,m-1)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            edges.PB(u,v,w);
            mat[u][v]=mat[v][u]=w;
        }
        int sum=kruskal();
        rep(i,0,n-1)
            dfs(i,i,-1);
        int q;
        scanf("%d",&q);
        double ans=0;
        rep(i,0,q-1)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            if(used[u][v]!=-1) ans+=sum-used[u][v]+min(dp[u][v], w);
            else ans+=sum;
        }
        printf("%.4lf\n",ans/(1.0*q));
    }
    return 0;
}