「日常訓練」 Genghis Khan the Conqueror(HDU-4126)
阿新 • • 發佈:2018-11-19
題意
給定\(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]\)
最後對每個查詢做修正就可以了,具體見程式碼。真實樹形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; }