概率dp总结
- 概率一般是正推,期望一般是逆推.
- 很多题要推公式,不能上去就dp.
- 可以利用矩阵快速幂加速,高斯消元解方程,待定系数法推公式。
- 超内存要想到滚动数组。
- 形成环要迭代推公式
- 树上的问题要讨论叶子节点和非叶子节点的递推公式,用待定系数法求系数,dfs爆搜。
- 有关概率的问题,应该要从反面多思考,逆向思考,从不同角度思考。
1.POJ3744
题意:起点是1,每次有p的概率走一步,或者1-p的概率走两步,路径上会有地雷,问你走出去的概率。
思路:××dp[1]=1,dp[i]=dp[i-1] × p+dp[i-2]×(i-p)××;
范围这么大,可以××矩阵快速幂优化递推××,当时路径上点有地雷该怎么办?分段求:1-x[1],x[1]+1-x[2]…
那么安全走完第一段的概率就是××1-p[x1]××
最后的答案累乘就行了。
总结:矩阵快速幂优化,正难则反
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=15;
const int mod=1e9+7;
int a[maxn],vis[maxn];
double dp[maxn];
int n;
double p;
struct mat
{
double m[2][2];
mat()
{
memset(m,0,sizeof m);
}
};
mat mul(mat a,mat b)
{
mat ans;
for(int i=0; i<=1; i++)
{
for(int j=0; j<=1; j++)
{
for(int k=0; k<=1; k++)
ans.m[i][j]=ans.m[i][j]+a.m[i][k]×b.m[k][j];
}
}
return ans;
}
mat qpow(ll b)
{
mat ans;
for(int i=0; i<=1; i++)
ans.m[i][i]=1.0;
mat base;
base.m[0][0]=p,base.m[0][1]=1.0-p;
base.m[1][0]=1.0,base.m[1][1]=0;
while(b)
{
if(b&1)
ans=mul(ans,base);
base=mul(base,base);
b>>=1;
}
return ans;
}
int main()
{
while(~scanf("%d%lf",&n,&p))
{
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
double ans=1.0;
a[0]=0;
for(int i=0; i<n; i++)
ans=ans×(1.0-qpow(a[i+1]-a[i]-1).m[0][0]);
printf("%.7f\n",ans);
}
}
2.POJ2096
题意:一个软件有s个子系统,会产生n种bug
某人一天发现一个bug,这个bug属于一个子系统,属于一个分类,每个bug属于某个子系统的概率是1/s ,属于某种分类的概率是1/n, 问发现n种bug,每个子系统都发现bug的天数的期望。
思路:考虑逆推dp[i][j]表示已经找到i种bug,j个系统的bug,达到目标状态的天数的期望,dp[n][s]=0;要求的答案是dp[0][0];
dp[i][j]可以转化成以下四种状态:
dp[i][j],发现一个bug属于已经有的i个分类和j个系统.
dp[i][j+1],发现一个bug属于已有的分类,不属于已有的系统
dp[i+1][j],发现一个bug属于已有的系统,不属于已有的分类
dp[i+1][j+1],发现一个bug不属于已有的系统,不属于已有的分类
×对应的概率移项就能逆推出dp[0][0];
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int mod=1e9+7;
int n,m;
double dp[maxn][maxn];
int main()
{
while(~scanf("%d%d",&n,&m))
{
dp[n][m]=0;
for(int i=n;i>=0;i--)
{
for(int j=m;j>=0;j--)
{
if(i==n&&j==m)continue;
double p1=(i×j)×1.0/(n×m);
double p2=((n-i)×j)×1.0/(n×m);
double p3=(i×(m-j))×1.0/(n×m);
double p4=((n-i)×(m-j))×1.0/(n×m);
//cout<<p1<<" "<<p2<<" "<<p3<<" "<<p4<<endl;
dp[i][j]=(dp[i+1][j]×p2+dp[i][j+1]×p3+dp[i+1][j+1]×p4+1.0)/(1-p1);
}
}
printf("%.4f\n",dp[0][0]);
}
}
3.ZOJ3329
题意:有三个骰子,分别有k1,k2,k3个面。
每次掷骰子,如果三个面分别为a,b,c则分数置0,否则加上三个骰子的分数之和。
当分数大于n时结束。求游戏的期望步数。初始分数为0。
思路:设dp[i]表示达到i分时到达目标状态的期望,答案就是dp[0];
则dp[i]=∑(pk × dp[i+k])+dp[0] × p0+1;
待定系数法设dp[i]=A[i] × dp[0]+B[i];
代入上述方程右边得到:
dp[i]=∑(pk×A[i+k]×dp[0]+pk×B[i+k])+dp[0]×p0+1
=(∑(pk×A[i+k])+p0)dp[0]+∑(pk×B[i+k])+1;
明显A[i]=(∑(pk×A[i+k])+p0)
B[i]=∑(pk×B[i+k])+1
先递推求得A[0]和B[0].
那么 dp[0]=B[0]/(1-A[0]);
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn=605;
const int mod=998244353;
int n;
double A[maxn],B[maxn],p[maxn];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,k1,k2,k3,a,b,c;
scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
int ksum=k1+k2+k3;
double p0=1.0/(k1×k2×k3);
memset(p,0,sizeof p);
for(int i=1; i<=k1; i++)
{
for(int j=1; j<=k2; j++)
{
for(int k=1; k<=k3; k++)
{
if(i!=a||j!=b||k!=c)
p[i+j+k]+=p0;
}
}
}
memset(A,0,sizeof A);
memset(B,0,sizeof B);
for(int i=n;i>=0;i--)
{
for(int j=3;j<=ksum;j++)
{
A[i]+=A[i+j]×p[j];
B[i]+=B[i+j]×p[j];
}
A[i]+=p0;
B[i]+=1;
}
printf("%.16f\n",B[0]/(1-A[0]));
}
}
4.HDU4405
题意:掷色子决定走几步,某些点有传送门(只能传送一次)😚到达终点的期望步数。
思路:dp[i]=dp[i+k]×p+1,逆推求dp[0],传送门直接就是dp[i]=dp[nxt[i]];
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn=2e5+5;
const int mod=998244353;
int n,m,nxt[maxn],x,y;
double dp[maxn];
int main()
{
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
double p0=1.0/6;
for(int i=1;i<=n;i++)nxt[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d",&x,&y),nxt[x]=y;
memset(dp,0,sizeof dp);
for(int i=n-1;i>=0;i--)
{
for(int j=1;j<=6;j++)
{
dp[i]+=dp[i+j]×p0;
}
dp[i]+=1.0;
//dp[i]=min(dp[i],dp[nxt[i]]);
dp[i]=dp[nxt[i]];
}
printf("%.4f\n",dp[0]);
}
}
5.HDU4089
题意:有n个人排队等着在官网上激活游戏。Tomato排在第m个。
对于队列中的第一个人。有一下情况:
1、激活失败,留在队列中等待下一次激活(概率为p1)
2、失去连接,出队列,然后排在队列的最后(概率为p2)
3、激活成功,离开队列(概率为p3)
4、服务器瘫痪,服务器停止激活,所有人都无法激活了。
求服务器瘫痪时Tomato在队列中的位置<=k的概率。
思路:
设dp[i][j]表示i个人排队,Tomato排在第j个位置,达到目标状态的概率(j<=i)
dp[n][m]就是所求
j== 1: dp[i][1]=p1×dp[i][1]+p2×dp[i][i]+p4;
2<=j<=k: dp[i][j]=p1×dp[i][j]+p2×dp[i][j-1]+p3×dp[i-1][j-1]+p4;
k<j<=i: dp[i][j]=p1×dp[i][j]+p2×dp[i][j-1]+p3×dp[i-1][j-1];
化简:
j==1: dp[i][1]=p×dp[i][i]+p41;
2<=j<=k: dp[i][j]=p×dp[i][j-1]+p31×dp[i-1][j-1]+p41;
k<j<=i: dp[i][j]=p×dp[i][j-1]+p31×dp[i-1][j-1];
其中:
p=p2/(1-p1);
p31=p3/(1-p1)
p41=p4/(1-p1)
可以循环i=1->n 递推求解dp[i].在求解dp[i]的时候dp[i-1]就相当于常数了。
在求解dp[i][1~i]时等到下列i个方程
j==1: dp[i][1]=p×dp[i][i]+c[1];
2<=j<=k:dp[i][j]=p×dp[i][j-1]+c[j];
k<j=i: dp[i][j]=p×dp[i][j]+c[j];
其中c[j]都是常数了。上述方程可以解出dp[i]了。
首先是迭代得到 dp[i][i].然后再代入就可以得到所有的dp[i]了。
注意特判一种情况。就是p4<eps时候,就不会崩溃了,应该直接输出0。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=2005;
double dp[2][maxn];
double p[maxn],c[maxn],p1,p2,p3,p4;
int n,m,k;
int main()
{
while(~scanf("%d%d%d%lf%lf%lf%lf",&n,&m,&k,&p1,&p2,&p3,&p4))
{
if(p3<1e-5)
{
printf("%.5f\n",0);
continue;
}
double p21=p2/(1-p1);
double p41=p4/(1-p1);
double p31=p3/(1-p1);
dp[1][1]=p41/(1-p21);
p[0]=1.0;
for(int i=1;i<=n;i++)
p[i]=p[i-1]*p21;
for(int i=2;i<=n;i++)
{
c[1]=p41;
for(int j=2;j<=k;j++)
c[j]=p31*dp[(i-1)&1][j-1]+p41;
for(int j=k+1;j<=n;j++)
c[j]=p31*dp[(i-1)&1][j-1];
double sum=c[1]*p[i-1];
for(int j=2;j<=i;j++)
sum=sum+c[j]*p[i-j];
dp[i&1][i]=sum/(1-p[i]);
dp[i&1][1]=p21*dp[i&1][i]+c[1];
for(int j=2;j<i;j++)
dp[i&1][j]=p21*dp[i&1][j-1]+c[j];
}
printf("%.5f\n",dp[n&1][m]);
}
}
6.HDU4035
题意:
有n个房间,由n-1条隧道连通起来,实际上就形成了一棵树,
从结点1出发,开始走,在每个结点i都有3种可能:
1.被杀死,回到结点1处(概率为ki)
2.找到出口,走出迷宫 (概率为ei)
3.和该点相连有m条边,随机走一条
求:走出迷宫所要走的边数的期望值。
设 E[i]表示在结点i处,要走出迷宫所要走的边数的期望。E[1]即为所求。
叶子结点:
E[i] = ki*E[1] + ei*0 + (1-ki-ei)*(E[father[i]] + 1);
= ki*E[1] + (1-ki-ei)*E[father[i]] + (1-ki-ei);
非叶子结点:(m为与结点相连的边数)
E[i] = ki*E[1] + ei*0 + (1-ki-ei)/m*( E[father[i]]+1 + ∑( E[child[i]]+1 ) );
= ki*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei)/m*∑(E[child[i]]) + (1-ki-ei);
设对每个结点:E[i] = Ai*E[1] + Bi*E[father[i]] + Ci;
对于非叶子结点i,设j为i的孩子结点,则
∑(E[child[i]]) = ∑E[j]
= ∑(Aj*E[1] + Bj*E[father[j]] + Cj)
= ∑(Aj*E[1] + Bj*E[i] + Cj)
带入上面的式子得
(1 - (1-ki-ei)/m*∑Bj)*E[i] = (ki+(1-ki-ei)/m*∑Aj)*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei) + (1-ki-ei)/m*∑Cj;
由此可得
Ai = (ki+(1-ki-ei)/m*∑Aj) / (1 - (1-ki-ei)/m*∑Bj);
Bi = (1-ki-ei)/m / (1 - (1-ki-ei)/m*∑Bj);
Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj);
对于叶子结点
Ai = ki;
Bi = 1 - ki - ei;
Ci = 1 - ki - ei;
从叶子结点开始,直到算出 A1,B1,C1;
E[1] = A1*E[1] + B1*0 + C1;
所以
E[1] = C1 / (1 - A1);
若 A1趋近于1则无解...
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=10005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m;
double k[maxn],e[maxn],A[maxn],B[maxn],C[maxn];
void dfs(int u,int fa)
{
int m=G[u].size();
A[u]=k[u];
B[u]=(1-k[u]-e[u])/m;
C[u]=(1-k[u]-e[u]);
double down=0;
for(auto v:G[u])
{
if(v==fa)
continue;
dfs(v,u);
// if(!dfs(v,u))
// return 0;
A[u]+=(1-k[u]-e[u])/m*A[v];
C[u]+=(1-k[u]-e[u])/m*C[v];
down+=(1-k[u]-e[u])/m*B[v];
}
// if(fabs(1-down)<eps)
// return 0;
A[u]/=(1-down);
B[u]/=(1-down);
C[u]/=(1-down);
// return 1;
}
int main()
{
int t,o=1;
cin>>t;
while(t--)
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
G[i].clear();
for(int i=1; i<n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1; i<=n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
k[i]=u*1.0/100;
e[i]=v*1.0/100;
}
dfs(1,-1);
if(fabs(A[1]-1)>eps)
{
printf("Case %d: %.10f\n",o++,C[1]/(1-A[1]));
}
else
printf("Case %d: impossible\n",o++);
}
}
7.HDU3853
题意:迷宫是一个R*C的布局,每个格子中给出停留在原地,往右走一个,往下走一格的概率,起点在(1,1),终点在(R,C),每走一格消耗两点能量,求出最后所需要的能量期望。
思路:dp[i][j]=p1[i][j]*dp[i][j]+p2[i][j]*dp[i][j+1]+p3[i][j]*dp[i+1][j]+2; 化简得到: dp[i][j]=p2[i][j]*dp[i][j+1]/(1-p1[i][j])+p3[i][j]*dp[i+1][j]/(1-p1[i][j])+2/(1-p1[i][j]);
注意一种情况就是p1[i][j]==1的情况。
题目只是保证答案小于1000000.但是有的点可能永远都不可能到达的。
所以这样的点出现p1[i][j]是允许的。
否则就会WA了。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=1005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m;
double p0[maxn][maxn],p1[maxn][maxn],p2[maxn][maxn];
double dp[maxn][maxn];
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lf%lf%lf",&p0[i][j],&p1[i][j],&p2[i][j]);
memset(dp,0,sizeof dp);
for(int i=n;i>=1;i--)
{
for(int j=m;j>=1;j--)
{
if(i==n&&j==m)continue;
if(fabs(p0[i][j]-1)<eps)continue;
dp[i][j]=(dp[i+1][j]*p2[i][j]+dp[i][j+1]*p1[i][j]+2)/(1-p0[i][j]);
}
}
printf("%.3f\n",dp[1][1]);
}
}
8.POJ 2151
题意:
ACM比赛中,共M道题,T个队,pij表示第i队解出第j题的概率
问 每队至少解出一题且冠军队至少解出N道题的概率。
解析:
设dp[i][j][k]表示第i个队在前j道题中解出k道的概率
则:
dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]);
先初始化算出dp[i][0][0]和dp[i][j][0];
设s[i][k]表示第i队做出的题小于等于k的概率
则s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k];
则每个队至少做出一道题概率为P1=(1-s[1][0])*(1-s[2][0])*(1-s[T][0]);
每个队做出的题数都在1~N-1的概率为P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]);
最后的答案就是P1-P2``
#include<iostream>
#include<cstring>
#include<cstdio>
#include<math.h>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=10005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m,sum;
double dp[1005][40][40];
double dp2[1005][40];
double p[1005][40];
int main()
{
while(cin>>m>>n>>sum)
{
if(n==0&&m==0&&sum==0)
break;
memset(dp,0,sizeof dp);
memset(dp2,0,sizeof dp2);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
scanf("%lf",&p[i][j]);
for(int i=1; i<=n; i++)
{
dp[i][0][0]=1.0;
for(int j=1;j<=m;j++)
dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]);
for(int j=1; j<=m; j++)
{
for(int k=1; k<=j; k++)
dp[i][j][k]=p[i][j]*dp[i][j-1][k-1]+(1-p[i][j])*dp[i][j-1][k];
}
dp2[i][0]=dp[i][m][0];
for(int k=1;k<=m;k++)
dp2[i][k]=dp[i][m][k]+dp2[i][k-1];
}
double p1=1.0;
double p2=1.0;
for(int i=1;i<=n;i++)
p1*=(1-dp2[i][0]);
for(int i=1;i<=n;i++)
p2*=(dp2[i][sum-1]-dp2[i][0]);
printf("%.3f\n",p1-p2);
}
}
题意:
原来袋子里有w只白鼠和b只黑鼠
龙和王妃轮流从袋子里抓老鼠。谁先抓到白色老师谁就赢。
王妃每次抓一只老鼠,龙每次抓完一只老鼠之后会有一只老鼠跑出来。
每次抓老鼠和跑出来的老鼠都是随机的。
如果两个人都没有抓到白色老鼠则龙赢。王妃先抓。
问王妃赢的概率。
解析:
设dp[i][j]表示现在轮到王妃抓时有i只白鼠,j只黑鼠,王妃赢的概率
明显 dp[0][j]=0,0<=j<=b;因为没有白色老鼠了
dp[i][0]=1,1<=i<=w;因为都是白色老鼠,抓一次肯定赢了。
dp[i][j]可以转化成下列四种状态:
1、王妃抓到一只白鼠,则王妃赢了,概率为i/(i+j);
2、王妃抓到一只黑鼠,龙抓到一只白色,则王妃输了,概率为j/(i+j)*i/(i+j-1).
3、王妃抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只黑鼠,则转移到dp[i][j-3]。
概率为j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2);
4、王妃抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只白鼠,则转移到dp[i-1][j-2].
概率为j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2);
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const double eps=1e-9;
vector<int>G[maxn];
int w,b,sum;
double dp[maxn][maxn];
int main()
{
cin>>w>>b;
for(int i=0; i<=b; i++)
dp[0][i]=0;
for(int i=1; i<=w; i++)
dp[i][0]=1;
for(ll i=1; i<=w; i++)
{
for(ll j=1; j<=b; j++)
{
dp[i][j]+=(i)*1.0/(i+j);
if(j>=2)
dp[i][j]+=(j*(j-1)*(i))*1.0/((i+j)*(i+j-1)*(i+j-2))*dp[i-1][j-2];
if(j>=3)
dp[i][j]+=(j*(j-1)*(j-2))*1.0/((i+j)*(i+j-1)*(i+j-2))*dp[i][j-3];
}
}
printf("%.10f\n",dp[w][b]);
}
排队取,第1个人取到1个,dp[1]=1;
后面的人dp[i]=p取到礼物盒子+dp取到礼物=(n-dp[i-1])/n + dp[i-1];
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e6+6;
const int mod=998244353;
const double eps=1e-9;
double dp[maxn];
int main()
{
int n,m;
while(cin>>n>>m)
{
dp[1]=1;
for(int i=2;i<=m;i++)
{
dp[i]=(1-dp[i-1])*dp[i-1]+dp[i-1]*(dp[i-1]-1.0/n);
}
double ans=0;
for(int i=1;i<=m;i++)
ans=ans+dp[i];
printf("%.10f\n",ans);
}
}
一只吸血鬼,有n条路给他走,每次他随机走一条路,
每条路有个限制,如果当时这个吸血鬼的攻击力大于
等于某个值,那么就会花费t天逃出去,否则,花费1天
的时间,并且攻击力增加,问他逃出去的期望
记忆化搜索:或者递推;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int mod=1e9+7;
const double eps=1e-5;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
double dp[maxn];int t[maxn];
int a[maxn],n,f;
double dfs(int now)
{
if(dp[now])
return dp[now];
for(int i=1;i<=n;i++)
{
if(now>a[i])
dp[now]+=t[i];
else
dp[now]+=(1+dfs(now+a[i]));
}
dp[now]/=n;
return dp[now];
}
int main()
{
while(cin>>n>>f)
{
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1; i<=n; i++)
t[i]=a[i]*a[i]*sgn;
for(int i=a[n]*2; i>=f; i--)
{
dp[i]=0;
for(int j=1; j<=n; j++)
{
if(i<=a[j])
dp[i]+=(1+dp[i+a[j]]);
else
dp[i]+=t[j];
}
dp[i]/=n;
dp[i]=0;
}
printf("%.3f\n",dfs(f));
}
}
HDU4436
有N(1<=N<=20)张卡片,每包中含有这些卡片的概率为p1,p2,````pN.
每包至多一张卡片,可能没有卡片。
求需要买多少包才能拿到所以的N张卡片,求次数的期望。
1、已经拥有
2、没有拥有
3、没有卡片
状态压缩dp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+1e5;
const int mod=1e9+7;
const double eps=1e-5;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
double p[maxn];
double dp[maxn],sum;
int n;
int main()
{
while(cin>>n)
{
sum=0;
for(int i=0; i<n; i++)
scanf("%lf",&p[i]),sum+=p[i];
sum=1-sum;
ll sta=(1<<n)-1;
for(int i=sta-1; i>=0; i--)
{
double p1=0,p2=1;
for(int j=0; j<n; j++)
{
if(i&(1<<j))
p1+=p[j];
else
p2+=dp[i|(1<<j)]*p[j];
}
dp[i]=p2/(1-sum-p1);
}
printf("%.5f\n",dp[0]);
}
}
题目:给出一个数轴,有一个起点和一个终点,某个人可以
走1,2,3……m步,每一种情况有一个概率,初始有一个方向,
走到头则返回,问到达终点的期望步数为多少。
高斯消元概率dp,dp[i]表示走到该点到达终点还需要的步数的期望。
那么有dp[i]=∑pk/100*(dp[i+k]+k)。
方程式很简单的,但是细节很多,具体见代码。
首先由于一些节点到达不了,需要bfs一遍编号。
有一个巧妙的方向处理就是虚拟出n-2个节点(除起点外其他点都有两个点),这样就不用考虑来回走了,加上对称性,如果开始方向为1,可以将起点sx变为cnt-sx,这样方向就不用处理了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=205;
const int mod=1e9+7;
const double eps=1e-9;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
int n,d,N,m,st,ed;
double a[maxn][maxn],x[maxn];
double p[maxn];
int num[maxn],equ,var,cnt;
int Gauss()
{
int i,j,k,col,max_r;
for(k=0,col=0; k<equ&&col<var; k++,col++)
{
max_r=k;
for(i=k+1; i<equ; i++)
if(fabs(a[i][col])>fabs(a[max_r][col]))
max_r=i;
if(fabs(a[max_r][col])<eps)
return 0;
if(k!=max_r)
{
for(j=col; j<var; j++)
swap(a[k][j],a[max_r][j]);
swap(x[k],x[max_r]);
}
x[k]/=a[k][col];
for(j=col+1; j<var; j++)
a[k][j]/=a[k][col];
a[k][col]=1;
for(i=0; i<equ; i++)
if(i!=k)
{
x[i]-=x[k]*a[i][k];
for(j=col+1; j<var; j++)
a[i][j]-=a[k][j]*a[i][col];
a[i][col]=0;
}
}
return 1;
}
void bfs(int st)
{
memset(num,-1,sizeof num);
queue<int>q;
q.push(st);cnt=0;
num[st]=cnt++;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=1; i<=m; i++)
{
int v=(i+u)%N;
if(p[i]<eps)
continue;
if(num[v]==-1)
num[v]=cnt++,q.push(v);
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d%d%d%d%d",&n,&m,&ed,&st,&d);
for(int i=1; i<=m; i++)
{
scanf("%lf",&p[i]);
p[i]/=100;
}
if(st==ed)
{
printf("0.00\n");
continue;
}
N=(n-1)*2;
if(d==1)
st=N-st;
bfs(st);
if(num[ed]==-1&&num[N-ed]==-1)
{
printf("Impossible !\n");
continue;
}
equ=var=cnt;
//cout<<cnt<<"***\n";
memset(a,0,sizeof a);
memset(x,0,sizeof x);
for(int i=0; i<N; i++)
{
if(num[i]!=-1)
{
if(i==ed||i==N-ed)
{
a[num[i]][num[i]]=1;
x[num[i]]=0;
continue;
}
a[num[i]][num[i]]=1;
for(int j=1; j<=m; j++)
{
int v=(i+j)%N;
if(num[v]==-1)
continue;
a[num[i]][num[v]]-=p[j];
x[num[i]]+=j*p[j];
}
}
}
if(Gauss())
printf("%.2f\n",x[num[st]]);
else
printf("Impossible !\n");
}
}