[概率与期望 && DP] BZOJ3640 .JC的小苹果

概率DP+高斯消元

fi,j 表示到 i 点,还剩 j 点血量的概率。

如果 ai>0 , fi,j=ikfk,j+ai 直接转移就好了

如果 ai=0 ,就要把 ai=0 的点构成联通块提出来高斯消元。

这样是 O(hp×n3) 的,发现树的形态是不变的,那么每次消元的矩阵除了常数项都不会变。

那么常数项可以用一个多项式代替,消出来的结果用多项式表示,DP的时候直接把常数项带进去就可以了。

消元复杂度是 O(n3) 的,DP转移就是 O(hp×n2)

注意到了n点后就不会走了……因为这个样例都搞了好久……

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;

const int N=160;

int n,m,hp,cnt,G[N],a[N],d[N],vis[N];
double *A[N],B[N][N],t[N*N],f[N][10010],v[N];
struct edge{
  int t,nx;
}E[10010];

inline void Insert(int x,int y){
  E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt; d[x]++;
  if(x==y) return ;
  E[++cnt].t=x; E[cnt].nx=G[y]; G[y]=cnt; d[y]++;
}

inline void Gauss(){
  for(int i=1;i<=n;i++){
    int t;
    for(int j=i;j<=n;j++)
      if(fabs(B[j][i])>1e-7){ t=j; break; }
    if(t!=i){
      for(int j=1;j<=n;j++) swap(B[i][j],B[t][j]);
      swap(A[i],A[t]);
    }
    for(int j=1;j<=n;j++){
      if(j==i) continue;
      B[i][j]/=B[i][i];
    }
    for(int j=1;j<=n;j++) A[i][j]/=B[i][i];
    B[i][i]=1;
    for(int j=1;j<=n;j++)
      if(fabs(B[j][i])>1e-7 && j!=i){
    double t=B[j][i];
    for(int k=1;k<=n;k++) B[j][k]-=t*B[i][k];
    for(int k=1;k<=n;k++) A[j][k]-=t*A[i][k];
      }
  }
}

int main(){
  scanf("%d%d%d",&n,&m,&hp);
  for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),A[i]=t+(i-1)*n+1;
  for(int i=1,x,y;i<=m;i++)
    scanf("%d%d",&x,&y),Insert(x,y);
  memset(t,0,sizeof(t)); memset(B,0,sizeof(B));
  f[1][hp]=1;
  for(int i=1;i<=n;i++)
    if(a[i]!=0) A[i][i]=1,B[i][i]=1;
    else{
      A[i][i]=1; B[i][i]=1;
      for(int j=G[i];j;j=E[j].nx)
    if(a[E[j].t]==0 && E[j].t!=n) B[i][E[j].t]-=1.0/d[E[j].t];
    }
  Gauss();
  for(int j=hp;j;j--){
    for(int i=1;i<=n;i++){
      if(!a[i] || a[i]+j>hp) continue;
      for(int k=G[i];k;k=E[k].nx)
    if(E[k].t!=n) f[i][j]+=1.0/d[E[k].t]*f[E[k].t][j+a[i]];
    }
    for(int i=1;i<=n;i++){
      v[i]=f[i][j];
      if(a[i]) continue;
      f[i][j]=0;
      for(int k=G[i];k;k=E[k].nx)
    if(a[E[k].t] && E[k].t!=n) v[i]+=1.0/d[E[k].t]*f[E[k].t][j];
    }
    for(int i=1;i<=n;i++){
      if(a[i]) continue;
      for(int k=1;k<=n;k++)
    f[i][j]+=v[k]*A[i][k];
    }
  }
  double ans=0;
  for(int i=1;i<=hp;i++) ans+=f[n][i];
  printf("%.8lf\n",ans);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值