hdu4418 Time Travel 概率DP+高斯消元

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zz_ylolita/article/details/81559547

可以先用BFS判断每个点是否可以到达,在BFS中使用队列和vis[],但是vis[]只用标记一次,并且即使点出队,标记也不要清除,因为如果一个点的vis = 1,那么它要么在队列里,当前不用入队,要么已经出队了,那么这个时候它能影响到的点已经被更新了,不用再入队一次。(和DFS同理)

设E[i]表示从i走到终点y的期望,那么E[y] = 0

来回折返的处理:为了将走动变成只有一个方向,将数轴翻折,比如0123-->01232(4)1(5)  最后没有0,就变成只会向右走了。这个时候起点还是只有一个,但是要根据方向处理一下是在左半部分还是在右半部分。但是终点会有两个,这两个终点的期望步数都是0

答案就是E[x]

根据概率DP的思想列出方程,然后套高斯消元的板子。

E[i] = sigma( (E[i+k] + k) * p[k]) k = 1..m

这是关于第i个点的方程,如果第i个点是两个终点之一,那么这个方程直接变成E[i] = 0;如果i是通过BFS找到的不可到达的点,那么E[i] = C(C为一个常数,随便写),相当于没有将第i个变元添加进方程组,实际上这个值可以任意填,不一定是设定的INF或者什么值,只要是个定值,就可以起到这样的效果。

此外,有很多小细节要注意:

题目中说 第一次走到y就算结束,那么如果一开始起点和终点就重合,答案一定是0.00,这点应该在判断每个点是否可以到达的时候全面地考虑到

为了套板子,先将n变成2n-2,这样方便一些

能否不用BFS判断一个点(终点)是否能被到达,直接用方程组是否有解来看呢?不能。因为这样方程就改变了,这个方程实际上是走了无穷次之后如果E[i]能够收敛,才能列出来的结果。如果直接列方程的话,就假设所有的点的期望都能收敛,这两个方程组关于真正能收敛的点不会是同解的。所以BFS是必要的。

//hdu4418
//浮点数线性方程组
//高斯消元
//倒着列方程
//E[i]表示从i走到y的期望步数,E[y] = 0,E[(n-y) % n] = 0
//答案为E[x]
//注意起点只有一个,但是终点有两个
//列方程为:E[i]=E[i+1]*p1+E[i+2]*p2+……E[i+m]*pm+1*p[1]+2*p[2]……m*p[m]
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <cmath>
using namespace std;
#define N 205
#define INF 0x3f3f3f3f
#define eps 1e-6
int n,m,y,x,d,T;
double p[N], tmp;
queue<int> q;
bool vis[N];
double ans[N], a[N][N];
bool bfs()
{
    memset(vis,0,sizeof(vis));
    q.push(x);
    vis[x] = 1;//只用标记一遍vis[]
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int i=1;i<=m;i++)
        {
            if (fabs(p[i]) < eps) continue;
            if (!vis[(u+i) % n])
            {
                vis[(u+i) % n] = 1;
                q.push((u+i) % n);
            }
        }
    }
    return vis[y] || vis[(n-y) % n];
}
void build()
{
    memset(a,0,sizeof(a));
    for (int i=0;i<n;i++)
    {
        a[i][i] = 1;
        if (!vis[i])
        {
            a[i][n] = INF;continue;
        }
        if (i == y || i == (n-y) % n)
        {
            a[i][n] = 0;continue;
        }
        a[i][n] = tmp;
        for (int j=1;j<=m;j++)
            a[i][(i+j) % n] -= p[j];
    }
}


void Debug()
{
    for (int i=0;i<n;i++)
    {
        for (int j=0;j<=n;j++)
            printf(".2f ", a[i][j]);
        printf("\n");
    }
    cout<<endl;
}

void swap(int x, int y)
{
    double t;
    for (int i=0;i<=n;i++)
    {
        t = a[x][i];a[x][i] = a[y][i];a[y][i] = t;
    }
}
int Gauss()
{
    int r = n;
    double t;
    for (int i=0;i<n;i++)
    {
        int pos = i;
        for (int j=i+1;j<n;j++)
            if (fabs(a[pos][i]) < fabs(a[j][i])) pos = j;

        if (fabs(a[pos][i]) < eps)
        {
            for (int j=i;j<n;j++)
                if (fabs(a[j][n]) >= eps) return 0;
            r--;continue;
        }
        if (pos != i) swap(pos,i);



        for (int j=i+1;j<n;j++)
            if (fabs(a[j][i]) >= eps)
        {
            t = a[j][i] / a[i][i];
            for (int k = i;k<=n;k++)
                a[j][k] -= t * a[i][k];
        }
    }
    for (int i=n-1;i>=0;i--)
    {
        ans[i] = a[i][n] / a[i][i];
        for (int j=i-1;j>=0;j--)
            a[j][n] -= ans[i] * a[j][i];
    }
    return r;
}
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d%d%d%d", &n, &m, &y, &x, &d);
        tmp = 0;
        for (int i=1;i<=m;i++)
        {
            scanf("%lf", &p[i]);
            p[i] *= 0.01;
            tmp += p[i] * i;
        }
        if (x == y)//根据题意
        {
            printf("0.00\n");
            continue;
        }
        n = 2*n-2;
        if (d == 1) x = (n - x) % n;//注意0
        if (!bfs())
            printf("Impossible !\n");
        else
        {
            build();//列方程
            if (!Gauss())
                printf("Impossible !\n");
            else
                printf("%.2f\n", ans[x]);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/zz_ylolita/article/details/81559547
今日推荐