【BZOJ】1778: [Usaco2010 Hol]Dotp 驱逐猪猡
【题意】给定无向图,炸弹开始在1,在每个点爆炸概率Q=p/q,不爆炸则等概率往邻点走,求在每个点爆炸的概率。n<=300。
【算法】概率+高斯消元
【题解】很直接的会考虑假设每个点爆炸的概率,无法转移。每个点不爆炸的概率,也无法转移。
因为爆炸概率相同,那么每个点爆炸的概率应该和到达该点的概率正相关。(另一种思路是和到达次数正相关)
设f[x]表示炸弹到达点x的概率(之前不爆炸)。
考虑枚举点x的下一步,发现无法用点y的概率来转移(因为f[y]可能由别的路走到)。
考虑枚举点x的上一步,根据全概率公式P(A)=P(Bi)*P(A|Bi):
$$f[x]=\sum_{y}\frac{f[y]*(1-Q)}{out[y]} \ \ , \ \ y \rightarrow x$$
理解:依赖于每一个可以到达x的点y,P(Bi)就是f[y],在到达y的前提下到达x的概率就是P(A|Bi)=(1-Q)/out[y]。
(另一种理解,依赖于每一条可以到达x的边,P(Bi)=f[y]*(1-Q)/out[y],P(A|Bi)=1)
特别的,点1还可以从天而降(概率为1),所以f[1]++。
最后ans[x]=f[x]*Q。或者根据炸弹最终爆炸概率为1,算Σf[i]后均分概率。
此题还可以计算每个点到达的期望次数,也是正相关。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; const int maxn=310; long double a[maxn][maxn]; int n,m,pp,qq,out[maxn]; void gauss(){ for(int i=1;i<n;i++){ int r=i; for(int j=i+1;j<=n;j++)if(fabs(a[j][i])>fabs(a[r][i]))r=j;////// if(r!=i)for(int j=i;j<=n+1;j++)swap(a[r][j],a[i][j]); for(int j=i+1;j<=n;j++){ for(int k=n+1;k>=i;k--){ a[j][k]-=a[j][i]/a[i][i]*a[i][k]; } } } for(int i=n;i>=1;i--){ for(int j=i+1;j<=n;j++)a[i][n+1]-=a[i][j]*a[j][n+1]; a[i][n+1]/=a[i][i]; } } int main(){ int pp,qq; long double Q; scanf("%d%d%d%d",&n,&m,&pp,&qq); Q=1.0*pp/qq; for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); a[v][u]+=(1-Q); if(u!=v)a[u][v]+=(1-Q); out[u]++;out[v]++; } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)if(out[j])a[i][j]=a[i][j]/out[j]; a[i][i]--; } a[1][n+1]=-1; gauss(); for(int i=1;i<=n;i++)printf("%.9Lf\n",a[i][n+1]*Q+(1e-13));/////////////// return 0; }
注意:
1.高斯消元过程中每次要找绝对值最大的主元,这是为了避免除零,提高精度。
2.涉及负数的浮点数最后要避免-0,加eps。