UVALive 4128 Steam Roller(多状态最短路)

题目:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2129

题目大意:给你一张格子图,r 根横线, c 根竖线。告诉你起点和终点,然后从起点走,每条边有权值,如果是0,就表示无法通行。走的规则是(通俗点):车子的惯性比较大,如果你在下个路口要转弯,那么后半段就开慢了,好转弯,转弯完了之后,你要加速,那么前半段就慢了,这两种情况都会使这段路的时间加倍,但是如果一条路同时是这样,那么也只算两倍。起点这终点一个启动,一个是制动,那么和他们相连的第一条边也算两倍。问你最短时间,如果不行,就输出 “Impossible” 。

解题思路:最短路问题,关键是怎么设计状态。可是它的状态比较多,每个点是状态节点,那么它肯定有它的坐标 x、y,由于跟到达这个节点的方向有关,以判断是不是转弯,那么再来一维表示方向,还有就是如果同时前段路慢了和后段路慢了,那么也是两倍的问题,可以再加一维来表示到达这个节点的这段路有没有加倍过。这样状态就设计完成了,在写最短路就行了。但是注意:这里有坑,就是到达终点的时候,由于边权加倍规则的存在,那么到达终点了可以再往前走一段,再回来,也就是说,本来不是说到达终点的边也要加倍,但是并不是在更新的时候直接判断加倍,这样会有问题,我的处理方式是,终点和其他点的处理方式一样,从队列里拿粗来的时候,判断是不是终点,然后再看 doubled ,就是到达终点的这条边正常模式下有没有加倍,如果没有就加倍,然后更新 ans。

        看了点书上的思路,然后自己写的代码,发现,本人和 LRJ 的写法有很大的不同,他是给状态编号,然后建边,再原封不动地套模板,就是之前做好所有准备,然后直接套。而我是直接改模板写。有时间,我在敲敲 LRJ 的写法,看看哪种比较好。

        这道题目 RE 了一发,因为双向边的问题,然后又 WA 了一发,原因竟然是我最后在输出的时候,case 那里我换行了,然后在输出答案,害我检查了好久上面的代码。。。= =

代码如下:

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int INF = 0x0fffffff;

const int MAXN = 111;

struct Edge
{
    int t_x,t_y,val,next;
    int dir;
} edge[MAXN*MAXN*2*2];

int tot,head[MAXN][MAXN];

void init()
{
    memset(head,-1,sizeof(head));
    tot = 0;
}

void add_edge(int s_x,int s_y,int t_x,int t_y,int val,int dir)
{
    edge[tot].dir = dir;
    edge[tot].t_x = t_x;
    edge[tot].t_y = t_y;
    edge[tot].val = val;
    edge[tot].next = head[s_x][s_y];
    head[s_x][s_y] = tot++;
}

struct Node
{
    int x,y;
    int val;
    int last_dir,last_edge_val,doubled;
    Node(int a,int b,int c,int d,int e,int f)
    {
        x = a;y = b;val = c;last_dir = d;last_edge_val = e;doubled = f;
    }
    bool operator < (const Node& tmp) const
    {
        return val > tmp.val;
    }
};

int done[MAXN][MAXN][11][11];
int d[MAXN][MAXN][11][11];

int ans;

void dij(int r,int c,int r1,int c1,int r2,int c2)
{
    priority_queue<Node> q;
    memset(done,0,sizeof(done));
    for(int i = 0;i < r;i++)
        for(int j = 0;j < c;j++)
            for(int dir = 0;dir < 4;dir++)
                for(int k = 0;k < 2;k++)
                    d[i][j][dir][k] = INF;
    d[r1][c1][4][1] = 0;
    q.push(Node(r1,c1,0,4,0,1));
    while(!q.empty())
    {
        Node cur = q.top();
        q.pop();
        if(cur.x == r2 && cur.y == c2)
        {
            int tmp;
            if(cur.doubled)
                tmp = cur.val;
            else tmp = cur.val+cur.last_edge_val;
            ans = min(ans,tmp);
        }
        if(done[cur.x][cur.y][cur.last_dir][cur.doubled]) continue;
        done[cur.x][cur.y][cur.last_dir][cur.doubled] = 1;
        for(int e = head[cur.x][cur.y];e != -1;e = edge[e].next)
        {

            int xx = edge[e].t_x;
            int yy = edge[e].t_y;

            int val = edge[e].val;
            int dir = edge[e].dir;
            int old_dif = 0;
            if(!cur.doubled)
            {
                if(dir != cur.last_dir)
                    old_dif = 1;
            }
            int new_dif = 1;
            int next_doubled = 0;
            if(cur.last_dir != dir)
            {
                new_dif = 2;
                next_doubled = 1;
            }

            int tmp = d[cur.x][cur.y][cur.last_dir][cur.doubled]+new_dif*val+old_dif*cur.last_edge_val;

            if(d[xx][yy][dir][next_doubled] > tmp)
            {
                //printf("%d,%d,%d,%d,tmp = %d\n",cur.x,cur.y,xx,yy,tmp);
                d[xx][yy][dir][next_doubled] = tmp;
                q.push(Node(xx,yy,tmp,dir,val,next_doubled));
            }
        }
    }
}

int main()
{
    int cas = 0;
    int r,c,r1,c1,r2,c2;
    while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
    {
        if(r+c == 0) break;
        r1--;c1--;r2--;c2--;
        init();
        for(int i = 0;i < 2*r-1;i++)
        {
            int val;
            if(i&1)
            {
                for(int j = 0;j < c;j++)
                {
                    scanf("%d",&val);
                    if(val == 0) continue;
                    add_edge(i/2+1,j,i/2,j,val,3);
                    add_edge(i/2,j,i/2+1,j,val,1);
                }
            }
            else
            {
                for(int j = 0;j < c-1;j++)
                {
                    scanf("%d",&val);
                    if(val == 0) continue;
                    add_edge(i/2,j,i/2,j+1,val,0);
                    add_edge(i/2,j+1,i/2,j,val,2);
                }
            }
        }
        ans = INF;
        dij(r,c,r1,c1,r2,c2);
        printf("Case %d: ",++cas);

        if(ans >= INF) puts("Impossible");
        else printf("%d\n",ans);
    }
    return 0;
}

/*
2 2 1 1 2 2
10
9 10
9

4 4 1 1 4 4
10 10 10
9  0  0 10
0  0  0
9  0  0  10
9  0  0
0  9  0  10
0  9  9
*/

模仿 LRJ 书中的写法,注意 can_go 那里的条件限制,WA了好久,感觉还是比较适应自己的写法,代码如下:

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int INF = 0x0fffffff;

const int MAXN = 111*111*11*11;
const int MAXM = 111*111*2*11*4*11;

int dir_x[] = {0,1,0,-1},dir_y[] = {1,0,-1,0};
int inv[] = {2,3,0,1};

struct Edge
{
    int t,val,next;
}edge[MAXM];

struct Node
{
    int id,val;
    Node(int a,int b)
    {
        id = a;val = b;
    }
    bool operator < (const Node& tmp) const
    {
        return val > tmp.val;
    }
};

int d[MAXN];
int n;

struct Dij
{
    int tot,head[MAXN];
    void init()
    {
        tot = 0;
        memset(head,-1,sizeof(head));
    }

    void add_edge(int s,int t,int val)
    {
        edge[tot].t = t;
        edge[tot].val = val;
        edge[tot].next = head[s];
        head[s] = tot++;
    }

    bool done[MAXN];
    void dijkstra(int s)
    {
        priority_queue<Node> q;
        memset(done,0,sizeof(done));
        for(int i = 0;i <= n;i++) d[i] = INF;
        d[s] = 0;
        q.push(Node(s,0));
        while(!q.empty())
        {
            Node cur = q.top();
            q.pop();
            if(done[cur.id]) continue;
            done[cur.id] = 1;
            for(int i = head[cur.id];i != -1;i = edge[i].next)
            {
                int t = edge[i].t;
                int val = edge[i].val;
                int tmp = d[cur.id]+val;
                if(d[t] > tmp)
                {
                    d[t] = tmp;
                    q.push(Node(t,tmp));
                }
            }
        }
    }
} dij;

int id[111][111][11][11];

int get_id(int i,int j,int dir,int doubled)
{
    int& x = id[i][j][dir][doubled];
    if(x == 0) x = ++n;
    return x;
}

int grid[111][111][11];

int read_int()
{
    int x;
    scanf("%d",&x);
    return x;
}

int can_go(int i,int j,int val,int r,int c)
{
    if(i < 0 || i >= r || j < 0 || j >= c || val == 0) return 0;
    return 1;
}

int main()
{
    int cas = 0;
    int r,c,r1,c1,r2,c2;
    while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
    {
        if(r == 0 && c == 0) break;
        r1--;c1--;r2--;c2--;
        for(int i = 0;i < r;i++)
        {
            for(int j = 0;j < c-1;j++)
            {
                grid[i][j][0] = grid[i][j+1][2] = read_int();
            }
            if(i != r-1)
            {
                for(int j = 0;j < c;j++)
                    grid[i][j][1] = grid[i+1][j][3] = read_int();
            }
        }
        dij.init();
        n = 0;
        memset(id,0,sizeof(id));
        for(int dir = 0;dir < 4;dir++)
        {
            int ii = r1+dir_x[dir];
            int jj = c1+dir_y[dir];
            if(can_go(ii,jj,grid[r1][c1][dir],r,c))
                dij.add_edge(0,get_id(ii,jj,dir,1),grid[r1][c1][dir]*2);
        }
        for(int i = 0;i < r;i++)
            for(int j = 0;j < c;j++)
                for(int last_dir = 0;last_dir < 4;last_dir++)
                    for(int doubled = 0;doubled < 2;doubled++)
                    {
                        for(int new_dir = 0;new_dir < 4;new_dir++)
                        {
                            int ii = i+dir_x[new_dir];
                            int jj = j+dir_y[new_dir];
                            if(!can_go(ii,jj,grid[i][j][inv[last_dir]],r,c)) continue;
                            if(!can_go(ii,jj,grid[i][j][new_dir],r,c)) continue;
                            int new_dif = 1;
                            int new_doubled = 0;
                            int old_dif = 0;
                            if(last_dir != new_dir)
                            {
                                new_dif = 2;
                                new_doubled = 1;
                                if(!doubled)
                                old_dif = 1;
                            }
                            int val = old_dif*grid[i][j][inv[last_dir]]+new_dif*grid[i][j][new_dir];
                            dij.add_edge(get_id(i,j,last_dir,doubled),get_id(ii,jj,new_dir,new_doubled),val);
                        }
                    }
        dij.dijkstra(0);
        int ans = INF;
        for(int dir = 0;dir < 4;dir++) if(can_go(r2,c2,grid[r2][c2][inv[dir]],r,c))
            for(int doubled = 0;doubled < 2;doubled++)
            {
                int id = get_id(r2,c2,dir,doubled);
                if(doubled)
                    ans = min(ans,d[id]);
                else
                {
                    int tmp = d[id]+grid[r2][c2][inv[dir]];
                    ans = min(ans,tmp);
                }
            }
        printf("Case %d: ",++cas);
        if(ans >= INF) puts("Impossible");
        else printf("%d\n",ans);
    }
    return 0;
}

/*

1 2 1 1 1 2
0

2 2 1 1 2 2
10
9 10
9

1 4 1 1 1 4
 10  10  10

4 4 1 1 4 4
 10  10  10
9  0  0  10
  0  0  0
9  0  0  10
  9  0  0
0  9  0  10
  0  9  9

2 2 1 1 2 2 0 1 1 0

*/

书上的第二种解法:除了四个方向以外,再增加一个方向:静止,那么状态就是 d(i,j,0~4)。点分静点和动点。一个动点可以变成以之前方向相同的静点,边权*2,,也可以变成同个运动方向的动点,边权*1;静点能变成各个方向的动点,也能直接变成静点(不叠加),边权都*2。这样一来,思路就比较清楚了,这种增加一个静止方向的方法值得学习。

代码如下:

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int INF = 0x0fffffff;

const int MAXN = 111;
const int MAXM = 111*111*2*2;

struct Edge
{
    int t_x,t_y,next,val,dir;
}edge[MAXM];

int tot,head[MAXN][MAXN];

void add_edge(int s_x,int s_y,int t_x,int t_y,int val,int dir)
{
    edge[tot].dir = dir;
    edge[tot].val = val;
    edge[tot].t_x = t_x;
    edge[tot].t_y = t_y;
    edge[tot].next = head[s_x][s_y];
    head[s_x][s_y] = tot++;
}

void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}

struct Node
{
    int x,y,dir,val;
    Node(int a,int b,int c,int d)
    {
        x = a;y = b;
        dir = c;val = d;
    }
    bool operator < (const Node& tmp) const
    {
        return val > tmp.val;
    }
};

int done[MAXN][MAXN][11],d[MAXN][MAXN][11];

void dij(int r1,int c1,int r2,int c2,int r,int c)
{
    priority_queue<Node> q;
    memset(done,0,sizeof(done));
    for(int i = 0;i < r;i++)
        for(int j = 0;j < c;j++)
            for(int dir = 0;dir < 5;dir++)
                d[i][j][dir] = INF;
    d[r1][c1][4] = 0;
    q.push(Node(r1,c1,4,0));
    while(!q.empty())
    {
        Node cur = q.top();
        q.pop();
        if(done[cur.x][cur.y][cur.dir]) continue;
        done[cur.x][cur.y][cur.dir] = 1;
        //printf("x = %d,y = %d,val = %d,dir = %d\n",cur.x,cur.y,cur.val,cur.dir);
        if(cur.dir == 4)
        {
            for(int i = head[cur.x][cur.y];i != -1;i = edge[i].next)
            {
                int xx = edge[i].t_x;
                int yy = edge[i].t_y;
                int dir = edge[i].dir;
                int tmp = cur.val+edge[i].val*2;
                if(tmp < d[xx][yy][dir])
                {
                    d[xx][yy][dir] = tmp;
                    q.push(Node(xx,yy,dir,tmp));
                }
                if(tmp < d[xx][yy][4])
                {
                    d[xx][yy][4] = tmp;
                    q.push(Node(xx,yy,4,tmp));
                }
            }
        }
        else
        {
            for(int i = head[cur.x][cur.y];i != -1;i = edge[i].next)
            {

                if(edge[i].dir == cur.dir)
                {
                    int xx = edge[i].t_x;
                    int yy = edge[i].t_y;
                    int tmp = cur.val+edge[i].val;
                    if(tmp < d[xx][yy][cur.dir])
                    {
                        d[xx][yy][cur.dir] = tmp;
                        q.push(Node(xx,yy,cur.dir,tmp));
                    }
                    tmp = cur.val+edge[i].val*2;
                    if(tmp < d[xx][yy][4])
                    {
                        d[xx][yy][4] = tmp;
                        q.push(Node(xx,yy,4,tmp));
                    }
                }
            }
        }
    }
}

int main()
{
    int cas = 0;
    int r,c,r1,c1,r2,c2;
    while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
    {
        if(r+c == 0) break;
        r1--;c1--;r2--;c2--;
        init();
        for(int i = 0;i < 2*r-1;i++)
        {
            int val;
            if(i&1)
            {
                for(int j = 0;j < c;j++)
                {
                    scanf("%d",&val);
                    if(val == 0) continue;
                    add_edge(i/2,j,i/2+1,j,val,1);
                    add_edge(i/2+1,j,i/2,j,val,3);
                }
            }
            else
            {
                for(int j = 0;j < c-1;j++)
                {
                    scanf("%d",&val);
                    if(val == 0) continue;
                    add_edge(i/2,j,i/2,j+1,val,0);
                    add_edge(i/2,j+1,i/2,j,val,2);
                }
            }
        }
        dij(r1,c1,r2,c2,r,c);
        printf("Case %d: ",++cas);
        int ans = d[r2][c2][4];
        if(ans >= INF) puts("Impossible");
        else printf("%d\n",ans);
    }
    return 0;
}

/*
2 2 1 1 2 2
10
9 10
9

1 4 1 1 1 4
10 10 10

4 4 1 1 4 4
 10  10  10
9  0  0  10
  0  0  0
9  0  0  10
  9  0  0
0  9  0  10
  0  9  9
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值