bzoj3640 JC的小苹果(期望dp+Gauss+矩阵求逆)

我们有比较显然的期望dp,f[x][i]表示到x点血量为i的期望次数。
我们有转移 f[x][i]=yf[y][i+w[x]]du[y] f [ x ] [ i ] = ∑ y f [ y ] [ i + w [ x ] ] d u [ y ]
直接高斯消元复杂度 O((nhp)3) O ( ( n h p ) 3 ) gg
由于血量不增,因此我们可以按剩余血量分层dp。
可是存在w[x]=0的点,每层内部还是需要一个高斯消元。复杂度 O(hpn3) O ( h p n 3 )
还是gg
我们发现每层内的高斯消元其实系数矩阵是相同的,只有常数矩阵在变。
即每次都是根据 Ax=B A x = B 求解列向量x,那么我们有 x=A1B x = A − 1 B ,即我们求出A矩阵的逆,每次只需把逆矩阵与常数矩阵乘起来即可得到x。因为常数矩阵也是一个列向量,所以每次的复杂度是 O(n2) O ( n 2 ) 的。
这样复杂度就优化到了 O(n3+hpn2) O ( n 3 + h p n 2 ) ,可以通过。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 160
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(T==S){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,m,K,h[N],num=0,du[N],w[N];
double f[N][10010],a[N][N],b[N],c[N][N],ans=0;
struct edge{
    int to,next;
}data[10010];
inline void inv(){
    for(int i=1;i<=n;++i) c[i][i]=1;
    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=1;j<=n;++j) swap(a[i][j],a[r][j]),swap(c[i][j],c[r][j]);
        double t=a[i][i];
        for(int j=1;j<=n;++j) a[i][j]/=t,c[i][j]/=t;
        for(int j=1;j<=n;++j){
            if(j==i) continue;t=a[j][i];
            for(int k=1;k<=n;++k) a[j][k]-=a[i][k]*t,c[j][k]-=c[i][k]*t;
        }
    }
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();K=read();
    for(int i=1;i<=n;++i) w[i]=read();
    while(m--){
        int x=read(),y=read();
        data[++num].to=y;data[num].next=h[x];h[x]=num;du[y]++;
        if(x==y) continue;
        data[++num].to=x;data[num].next=h[y];h[y]=num;du[x]++;
    }for(int x=1;x<=n;++x){
        a[x][x]=1;if(w[x]) continue;
        for(int i=h[x];i;i=data[i].next){
            int y=data[i].to;if(y==n) continue;
            a[x][y]-=1.0/du[y];
        }
    }inv();
    for(int k=K;k>=1;--k){
        memset(b,0,sizeof(b));if(k==K) b[1]=1;
        for(int x=1;x<=n;++x){
            if(!w[x]||w[x]+k>K) continue;
            for(int i=h[x];i;i=data[i].next){
                int y=data[i].to;if(y==n) continue;
                b[x]+=f[y][w[x]+k]/du[y];
            }
        }for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                f[i][k]+=c[i][j]*b[j];ans+=f[n][k];
    }printf("%.8lf\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值