【洛谷4009】汽车加油行驶问题(SPFA乱搞)

点此看题面

大致题意:给定一个(N*N)的方形网格,其中1表示这个格子有油库,0表示这个格子没油库,且汽车加满油可以行驶(k)条网格边。如果遇到油库必须加满油并花费(A)元,如果(X)坐标或(Y)坐标减少需花费(B)元,若需新建一个油库需花费(C)元(另需加油费(A)元)。问你从((1,1))((N,N))的最少花费。

(SPFA)做法

先说明,这篇博客只讲(SPFA),不讲网络流。

我们可以用(dis_{i,j,l})来表示到达网格((i,j)),还能行驶(l)条网格边所需的最小花费。

显然,初始化(dis_{1,1,k}=0),然后从((1,1))出发跑最短路即可。

对于当前状态((i,j,l)),我们可以这样考虑它的转移:

  • 如果当前还能行驶的距离不等于(k)(即油未加满)

    • 如果当前网格有油库,那么我们就可以花费(A)元将状态转移至((i,j,k)),即:

      [dis_{i,j,k}=min(dis_{i,j,k},dis_{i,j,l}+A) ]

    • 如果当前网格没有油库,那么我们就可以花费(C)元造一个油库,然后花费(A)元将状态转移至((i,j,k)),即:

      [dis_{i,j,k}=min(dis_{i,j,k},dis_{i,j,l}+A+C) ]

  • 如果当前还能行驶的距离大于0

    • 如果是向右或向下行驶,那么可以直接将状态转移至((i+1,j,l-1))((i,j+1,l-1)),即:

    [dis_{i+1,j,l-1}=min(dis_{i+1,j,l-1},dis_{i,j,l}) ]

    [dis_{i,j+1,l-1}=min(dis_{i,j+1,l-1},dis_{i,j,l}) ]

    • 如果是向左或向上行驶,那么就需要花费(B)元才能将状态转移至((i-1,j,l-1))((i,j-1,l-1)),即:

      [dis_{i-1,j,l-1}=min(dis_{i-1,j,l-1},dis_{i,j,l}+B) ]

      [dis_{i,j-1,l-1}=min(dis_{i,j-1,l-1},dis_{i,j,l}+B) ]

这样,代码就不难写了吧。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define N 100
#define K 10
#define MOD (N*N*K)
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
const int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int n,m,a,b,c,s[N+5][N+5],dis[N+5][N+5][K+5],Inqueue[N+5][N+5][K+5];
struct Status
{
    int x,y,v;
}q[MOD+5];
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline void read_digit(int &x)
{
    while(!isdigit(x=tc()));
    x-=48;
}
inline void write(int x)
{
    if(x>9) write(x/10);
    pc(x%10+'0');
}
int main()
{
    register int i,j,l,H=Inqueue[1][1][m]=1,T=1;
    for(read(n),read(m),read(a),read(b),read(c),i=1;i<=n;++i) for(j=1;j<=n;++j) read_digit(s[i][j]);
    for(i=1;i<=n;++i) for(j=1;j<=n;++j) for(l=0;l<=m;++l) dis[i][j][l]=1e9;//初始化
    dis[1][1][m]=0,q[1]=(Status){1,1,m};//初始化
    while((H%=MOD)^(T+1))//只要队列不为空(因为是手写的循环队列,因此看起来特别别扭)
    {
        Status k=q[H++];Inqueue[k.x][k.y][k.v]=0;//取出队首元素
        if(k.v^m)//如果油未加满
        {
            if(s[k.x][k.y])//如果这里有油库
            {
                if(dis[k.x][k.y][k.v]+a<dis[k.x][k.y][m]) 
                {
                    dis[k.x][k.y][m]=dis[k.x][k.y][k.v]+a;
                    if(!Inqueue[k.x][k.y][m]) Inqueue[k.x][k.y][m]=1,q[(++T)%=MOD]=(Status){k.x,k.y,m};
                } 
                continue;
            }//否则就没有油库,需要花C元建一个
            if(dis[k.x][k.y][k.v]+a+c<dis[k.x][k.y][m])
            {
                dis[k.x][k.y][m]=dis[k.x][k.y][k.v]+a+c;
                if(!Inqueue[k.x][k.y][m]) Inqueue[k.x][k.y][m]=1,q[(++T)%=MOD]=(Status){k.x,k.y,m};
            }
        }
        if(k.v)//如果还有油
        {
            for(i=0;i<4;++i)//枚举上下左右四个方向
            {
                static int nx,ny;
                if(dis[k.x][k.y][k.v]+(dx[i]<0||dy[i]<0)*b<dis[nx=k.x+dx[i]][ny=k.y+dy[i]][k.v-1])//如果是向左或向上,还需加上B元
                {
                    dis[nx][ny][k.v-1]=dis[k.x][k.y][k.v]+(dx[i]<0||dy[i]<0)*b;
                    if(!Inqueue[nx][ny][k.v-1]) Inqueue[nx][ny][k.v-1]=1,q[(++T)%=MOD]=(Status){nx,ny,k.v-1};
                }
            }
        }
    }
    register int ans=1e9;
    for(i=0;i<=m;++i) ans=min(ans,dis[n][n][i]);//枚举到达(N,N)后还能行驶的距离,从而求出最优方案下的最少花费
    return write(ans),fwrite(pp,1,pp_,stdout),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4009.html