[luogu2973]driving out the piggies 驱逐猪猡【高斯消元+概率DP】
看到题面的那一刻,我是绝望的ORZ
图论加概率期望加好像不沾边的高斯消元???我人直接傻掉
还没学过概率期望的我果断向题解屈服了(然后还是傻掉了两节课来找线性方程..
Description
奶牛们建立了一个随机化的臭气炸弹来驱逐猪猡。猪猡的文明包含1到N (2 <= N <= 300)一共N个猪城。这些城市由M (1 <= M <= 44,850)条由两个不同端点A_j和B_j (1 <= A_j<= N; 1 <= B_j <= N)表示的双向道路连接。保证城市1至少连接一个其它的城市。 一开始臭气弹会被放在城市1。每个小时(包括第一个小时),它有P/Q (1 <= P <=1,000,000; 1 <= Q <= 1,000,000)的概率污染它所在的城市。如果这个小时内它没有污染它所在的城市,那麽它随机地选择一条道路,在这个小时内沿着这条道路走到一个新的城市。可以离开这个城市的所有道路被选择的概率均等。 因为这个臭气弹的随机的性质,奶牛们很困惑哪个城市最有可能被污染。给定一个猪猡文明的地图和臭气弹在每个小时内爆炸的概率。计算每个城市最终被污染的概率。
solution
首先由一眼看穿法不难证出,城市被污染的概率等于该城市期望经过次数乘炸弹爆炸的概率。
那么城市的期望经过怎么求呢? 可以采用dp的思想,一个城市的概率经过次数可以通过累加可到达它的前置城市期望次数与转移到该城市的概率求得。
对于城市u的期望经过次数fu,有:
f[u]=Σ((1/out[v])×(1-p/q)×f[v]) (out为点的出度,u,v间连通)
但由于u,v互相转移,且转移u时v并没完成转移,所以不能按dp打,于是用高斯消元解线性多元方程组来解决这个问题。
可将转移方程看作:
f[v1]×a[v1]+f[v2]×a[v2]+...+f[vn]×a[vn]=f[u] ,(a[i]=(1/out[i])×(1-p/q))
移项,得:
-f[vu]+f[v1]×a[v1]+f[v2]×a[v2]+...+f[vn]×a[vn]=0
然后就能开始愉快地解方程了。
注意:因为出发点为城市1,所以城市1的期望经过次数应加一,所以f[1]系数为负的式子等号右应该是-1而不是0。
代码如下:
#include<bits/stdc++.h> using namespace std; const double eps=1e-14; double k,p,q,t,a[302][301]; int out[301],st[89701],to[89701],head[301],nex[89701],num,m,n; 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++) { double temp=a[j][i]/a[i][i]; for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*temp; } } for(int i=n;i>0;i--) { for(int j=i+1;j<=n;j++) a[i][n+1]-=a[j][n+1]*a[i][j]; a[i][n+1]/=a[i][i]; } } int main() { scanf("%d%d%lf%lf",&n,&m,&p,&q); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); st[++num]=x; to[num]=y; nex[num]=head[y]; head[y]=num;//用终点建边 st[++num]=y; to[num]=x; nex[num]=head[x]; head[x]=num; out[x]++; out[y]++; } k=p/q; for(int i=1;i<=n;i++) { a[i][i]=-1; for(int j=head[i];j!=0;j=nex[j]) a[i][st[j]]=(1-k)/out[st[j]]; } a[1][n+1]=-1; gauss(); for(int i=1;i<=n;i++) if(fabs(a[i][n+1])<eps) a[i][n+1]=0; for(int i=1;i<=n;i++) printf("%.9lf\n",a[i][n+1]*k); return 0; }