[BZOJ 1415][NOI 2005]聪聪和可可(SPFA+概率DP)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1415

思路

根据题目要求,聪聪每次从旧点 u 移动到新点 v v u 到可可当前所在的点 T 的最短路径上,与 u 相邻的那个点,并且若存在多条最短路径, v 的编号一定是所有满足条件的 v 中最小的。而且在一步之内,聪聪可以先跳到当前点 u 的一个后继 u 上,若 u 不是 T ,则聪聪可以再从 u 跳到新的 u 的后继 nextu 上,此时若 nextu 就是 T ,则可可被聪聪吃掉了,否则可可可以留在原地 T 不动,或者移动到 T 相邻的点上,概率都是一样的。
那么我们可以根据这些特点,首先通过 n 次SPFA,找出 pos[i][j] 数组,表示点 i 到点 j 的所有最短路中,与点 i 相邻,且编号最小的点。假如聪聪在点 i ,显然他会在下一时间跳到 pos[i][T] (跳一次就抓住了可可)或者 pos[pos[i][T][T] (跳一次抓不住可可)。我们通过DFS,找出聪聪在点 u ,可可在点 v 时,聪聪抓住可可的期望步数。这个DFS就是模拟聪聪和可可在下一时刻分别跑到了什么地方,将所有可能的后继状态的期望加起来并求平均值,即可得到本时刻的期望步数。简单表示当前状态的期望步数为 stepu,v 则有:

stepu,v=vvsteppos[pos[u][v]][v],v

显然这个DFS可以通过记忆化来优化,优化后这个做法就能AC

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXE 2100
#define MAXV 1100

using namespace std;

struct edge
{
    int u,v,w,next;
}edges[MAXE];

int head[MAXV],nCount=0;

void AddEdge(int U,int V,int W)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].w=W;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

int q[MAXE*2],dist[MAXV];
bool inQueue[MAXV];
int pos[MAXV][MAXV]; //pos[i][j]=i到j的最短路上与i相邻且编号最小的点编号
double f[MAXV][MAXV]; //f[i][j]=聪聪在点i,可可在点j,聪聪抓住可可所需期望步数

void SPFA(int S)
{
    memset(inQueue,false,sizeof(inQueue));
    memset(dist,0x3f,sizeof(dist));
    int h=0,t=0;
    dist[S]=0;
    for(int p=head[S];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        q[t++]=v;
        dist[v]=1;
        pos[S][v]=v; //!!!!!
        inQueue[v]=true;
    }
    while(h<t)
    {
        int u=q[h++];
        inQueue[u]=false;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(dist[u]+edges[p].w<dist[v])
            {
                dist[v]=dist[u]+edges[p].w;
                pos[S][v]=pos[S][u];
                if(!inQueue[v])
                {
                    q[t++]=v;
                    inQueue[v]=true;
                }
            }
            else if(dist[u]+edges[p].w==dist[v]&&pos[S][u]<pos[S][v])
            {
                pos[S][v]=pos[S][u];
                if(!inQueue[v])
                {
                    q[t++]=v;
                    inQueue[v]=true;
                }
            }
        }
    }
}

void DFS(int u,int v) //求f[u][v]
{
    if(f[u][v]>=0) return;
    int nextu=pos[pos[u][v]][v]; //一般情况下u在下一时刻走到的位置
    int cnt_child=0; //v的后继个数
    double sum=0; //sum=nextu到所有v的后继的期望步数之和
    if(nextu==v) //直接在下一时刻抓到了可可,那么吃到可可的期望步数就是1次
    {
        f[u][v]=1;
        return;
    }
    for(int p=head[v];p!=-1;p=edges[p].next)
    {
        int nextv=edges[p].v;
        DFS(nextu,nextv);
        sum+=f[nextu][nextv];
        cnt_child++;
    }
    DFS(nextu,v);
    sum+=f[nextu][v];
    cnt_child++;
    f[u][v]=sum/cnt_child+1; //u到nextu走了一步,所以要加1
}

int n,m;

int main()
{
    int S,T;
    scanf("%d%d%d%d",&n,&m,&S,&T);
    memset(head,-1,sizeof(head));
    memset(f,-1,sizeof(f));
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        AddEdge(u,v,1);
        AddEdge(v,u,1);
    }
    for(int i=1;i<=n;i++) //n次SPFA预处理出所有的pos[i][j]
    {
        f[i][i]=0; //相遇直接吃掉,无需移动
        pos[i][i]=i;
        SPFA(i);
    }
    DFS(S,T);
    printf("%.3lf\n",f[S][T]);
    return 0;
}

你可能感兴趣的:([BZOJ 1415][NOI 2005]聪聪和可可(SPFA+概率DP))