【BZOJ3143】[Hnoi2013]遊走 期望DP+高斯消元
阿新 • • 發佈:2017-06-18
結束 strong 思路 add tin clu long family continue
2 3
1 2
1 3
【BZOJ3143】[Hnoi2013]遊走
Description
一個無向連通圖,頂點從1編號到N,邊從1編號到M。
小Z在該圖上進行隨機遊走,初始時小Z在1號頂點,每一步小Z以相等的概率隨機選 擇當前頂點的某條邊,沿著這條邊走到下一個頂點,獲得等於這條邊的編號的分數。當小Z 到達N號頂點時遊走結束,總分為所有獲得的分數之和。
現在,請你對這M條邊進行編號,使得小Z獲得的總分的期望值最小。
Input
第一行是正整數N和M,分別表示該圖的頂點數 和邊數,接下來M行每行是整數u,v(1≤u,v≤N),表示頂點u與頂點v之間存在一條邊。 輸入保證30%的數據滿足N≤10,100%的數據滿足2≤N≤500且是一個無向簡單連通圖。
Output
僅包含一個實數,表示最小的期望值,保留3位小數。
Sample Input
3 32 3
1 2
1 3
Sample Output
3.333HINT
邊(1,2)編號為1,邊(1,3)編號2,邊(2,3)編號為3。題解:一個清晰的思路:我們如果能求出每條邊期望被經過的次數,然後排個序,讓期望次數越大的邊的編號越小就行了。但是問題來了,點數500,邊數?????,以邊為變量跑高斯消元顯然會TLE,那麽我們只能以點為變量跑高斯消元。那麽如何用點的期望表示邊的期望呢?其實很簡單,設邊(a,b),點a的期望被經過次數是f[a],點b的是f[b],a的度數是d[a],b的是d[b],那麽這條邊的期望被經過次數顯然是${f[a]\over d[a]}+{f[b]\over d[b]}$。
然後就是老辦法了,如果存在邊(a,b),那就f[a]+=f[b]/d[b],處理出來再移項即可,直接上高斯消元。
別忘了f[n]=1,f[1]要+1(因為是起始點)
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; typedef long double ld; int n,m; int pa[130000],pb[130000],d[510]; ld pc[130000],v[510][510],ans; void add(int a,int b) { if(a==n) return ; v[b][a]-=(ld)1/d[a]; } int main() { scanf("%d%d",&n,&m); int i,j,k; for(i=1;i<=m;i++) scanf("%d%d",&pa[i],&pb[i]),d[pa[i]]++,d[pb[i]]++; for(i=1;i<=m;i++) add(pa[i],pb[i]),add(pb[i],pa[i]); for(i=1;i<=n;i++) v[i][i]+=1; for(i=1;i<=n+1;i++) v[n][i]=0; v[n][n]=v[n][n+1]=1,v[1][n+1]+=1; for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) if(fabs(v[j][i])>fabs(v[i][i])) for(k=i;k<=n+1;k++) swap(v[i][k],v[j][k]); if(fabs(v[i][i])<1e-7) continue; for(j=n+1;j>=i;j--) v[i][j]/=v[i][i]; for(j=1;j<=n;j++) if(i!=j) { for(k=1;k<=n+1;k++) if(i!=k) v[j][k]-=v[j][i]*v[i][k]; v[j][i]=0; } } for(i=1;i<=m;i++) { if(pa[i]!=n&&fabs(v[pa[i]][pa[i]])>1e-7) pc[i]+=v[pa[i]][n+1]/d[pa[i]]; if(pb[i]!=n&&fabs(v[pb[i]][pb[i]])>1e-7) pc[i]+=v[pb[i]][n+1]/d[pb[i]]; } sort(pc+1,pc+m+1); for(i=1;i<=m;i++) ans+=(m-i+1)*pc[i]; printf("%.3lf",(double)ans); return 0; }
【BZOJ3143】[Hnoi2013]遊走 期望DP+高斯消元