HDU 4881 ZCC loves traffic lights 卡位最短路

原题:http://acm.hdu.edu.cn/showproblem.php?pid=4881

题意:给一个20*20的带权网格图,每个结点处有红绿灯,颜色为红 - 绿 - 红, 绿灯时可以随便走,红灯只能右转,或者闯一次红灯扣光12分,给出绿灯亮起的时间,求S到T的最短路(到达时间 - 出发时间最短)


解题思路:

首先如果这个题如果要求的是到达时间最小,就简单了,直接spfa搞定,虽然写起来还是有点恶心

dis[x][y][dir][msk]表示现在在(x, y), 行驶方向是dir, msk表示是否已经闯过红灯,直接转移就可以

每次判断当前是否为绿灯,不是的话能不能等到绿灯亮起(因为绿灯灭了一次就不会再亮了, 所以有等到天荒地老都没用的情况)

如果需要闯红灯的话,更新一下msk就好,右转只要把方向向量按顺序写好,就可以直接用 (pre + 1) % 4 == cur来判断了


那么我们主要需要处理就是,如何解决出发时间的问题

首先我们如果从0时刻出发,到达T的时刻肯定是最早的,想要缩短到达 - 出发的时间,就要延后出发时间,或者是出发到达都延后,但出发延后的更多

那么我们延后出发时间可以缩短路上耗时的原因就是,路上有等灯的情况(我总是会不自觉的脑补inter -_-!)

与其在路上等,不如晚点走,但是晚多少出发?枚举肯定是不行的

我们考虑在某个结点X出等灯,那么绿灯亮起后肯定是立刻出发的,也就是说我们卡着绿的亮起的瞬间从结点X继续走

另外一种情况就是我路过X的时候,绿灯还会亮很久,时间有些浪费,所以可以晚一些出发,在X处卡着绿灯结束的时间过去就可以了(来的早不如来的巧)


但是,问题是最优解一定会卡在一个点吗?

答案是一定的,我们刨除闯红灯和红灯右转的部分,走绿灯的部分都有一个绿灯区间,我们把整体的时刻平移,早晚会有一个点卡在绿灯边界

因为出发时间有0做下限,所以可能无法向左平移,但是向右是一定可以的,所以必然存在最优解,舍得他到达某一个点是卡在绿灯熄灭的时刻

如果全程都没走绿灯,那么情况相当于任意时刻出发都一样,我们在枚举卡位的时候,可以把终点的绿灯时间设一个小于inf又足够大的值,让他卡在终点的绿灯熄灭时间到达


于是我们可以枚举所有可以卡位的点和卡位时间,即有绿等会亮起的结点,设为M

那么我们的路径分为两条,一条是S - M,另一条是M - T

M - T这一段和本文最开始说的要求到达时间最短是一个问题,因为已经出发了

那么S - M这一段可以倒过来求M - S的最短路,也就是获得从S最晚什么时候出发

两个方向都需要记录闯红灯和不闯红灯的结果,然后再合并


但是我们有一个问题,如果倒着回去S的时候,遇到了红灯怎么处理,这个时间关系可能比较复杂

我的做法是回去的路段遇到红灯必闯

假设从M - S的路径上有一个点遇到了红灯,不闯红灯就说明要在这里等,而等就说明了在这里出发是卡着绿灯亮起的时间的

那么这种情况完全可以再枚举卡在这个点绿灯亮起时间时求得,所以:

必然存在一种卡位使得从M - S 一路畅通无阻(可以用掉那次闯红灯的机会)


于是对于M - S的路径搜不通的情况下,就没有必要再去搜M - T,同时由于大部分情况都是没法搜通M - S的,所以我的代码才跑了62MS


代码:

<span style="font-family:Courier New;">//
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

#define M 21
#define inf 0x3f3f3f3f
#define REP(i,a,b) for(int i = a; i <= b; ++i)

struct Node {
    int x, y, dir, msk;
    Node(){}
    Node(int x, int y, int dir, int msk):x(x), y(y), dir(dir), msk(msk) {}
};

int R[M][M], G[M][M], L[M][M][M][M], n, m;
int dis[M][M][4][2], in[M][M][4][2];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
int sx, sy, tx, ty;

bool left(int pre, int cur) {
    return ((cur + 1) & 3) == pre;
}

bool right(int pre, int cur) {
    return ((pre + 1) & 3) == cur;
}

bool back(int mx, int my, int t, int a[]) {
    queue<Node> Q;
    memset(dis, -1, sizeof(dis));
    REP(i,0,3) Q.push(Node(mx, my, i, 0)), in[mx][my][i][0] = true, dis[mx][my][i][0] = t;
    while( !Q.empty() ) {
        Node cur = Q.front(); Q.pop();
        in[cur.x][cur.y][cur.dir][cur.msk] = false;
        REP(k, 0, 3) {
            int nx = cur.x + dx[k];
            int ny = cur.y + dy[k];
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
            int ct = dis[cur.x][cur.y][cur.dir][cur.msk], nt = ct - L[cur.x][cur.y][nx][ny];
            int nmsk = cur.msk;
            if((ct <= R[cur.x][cur.y] || ct > G[cur.x][cur.y]) && !left(cur.dir, k)) ++nmsk;
            if(nmsk > 1 || dis[nx][ny][k][nmsk] >= nt) continue;
            dis[nx][ny][k][nmsk] = nt;
            if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true;
        }
    }
    a[0] = a[1] = -1;
    REP(i,0,3) REP(j,0,1) a[j] = max(a[j], dis[sx][sy][i][j]);
    if(max(a[0], a[1]) < 0) return false;
    return true;
}

void go(int mx, int my, int t, int b[]) {
    queue<Node> Q;
    memset(dis, 0x3f, sizeof(dis));
    REP(i,0,3) Q.push(Node(mx, my, i, 0)), in[mx][my][i][0] = true, dis[mx][my][i][0] = t;
    while( !Q.empty() ) {
        Node cur = Q.front(); Q.pop();
        in[cur.x][cur.y][cur.dir][cur.msk] = false;
        REP(k, 0, 3) {
            int nx = cur.x + dx[k];
            int ny = cur.y + dy[k];
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
            int ct = dis[cur.x][cur.y][cur.dir][cur.msk], dlt = L[cur.x][cur.y][nx][ny], nt;
            int nmsk = cur.msk;
            if(R[cur.x][cur.y] + 1 <= G[cur.x][cur.y] && ct <= G[cur.x][cur.y]) {
                nt = max(R[cur.x][cur.y] + 1, ct) + dlt;
                if(dis[nx][ny][k][nmsk] > nt) {
                    dis[nx][ny][k][nmsk] = nt;
                    if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true;
                }
            }
            if(!right(cur.dir, k)) ++nmsk;
            nt = ct + dlt;
            if(nmsk > 1 || dis[nx][ny][k][nmsk] <= nt) continue;
            dis[nx][ny][k][nmsk] = nt;
            if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true;
        }
    }
    b[0] = b[1] = inf;
    REP(i,0,3) REP(j,0,1) b[j] = min(b[j], dis[tx][ty][i][j]);
}

int cal(int mx, int my, int t) {
    int a[2], b[2];
    if(!back(mx, my, t, a)) return inf;
    go(mx, my, t, b);
    int ret = inf;
    REP(i,0,1) REP(j,0,1) if(i + j < 2) {
        if(a[i] != -1 && b[j] != inf)
            ret = min(ret, b[j] - a[i]);
    }
    return ret;
}

int solve() {
    int ret = inf;
    REP(i,1,n) REP(j,1,m) if(R[i][j] + 1 <= G[i][j]) {
        ret = min(ret, cal(i, j, G[i][j]));
    }
    return ret == inf ? -1 : ret;
}

int main()
{
    int x, cas = 0;
    while( scanf( "%d%d", &n, &m ) != EOF ) {
        REP(i,1,n) REP(j,1,m) scanf( "%d", &R[i][j] );
        REP(i,1,n) REP(j,1,m) scanf( "%d", &G[i][j] );
        REP(i,1,n) REP(j,1,m - 1) {
            scanf( "%d", &x );
            L[i][j][i][j+1] = L[i][j+1][i][j] = x;
        }
        REP(i,1,n - 1) REP(j,1,m) {
            scanf( "%d", &x );
            L[i][j][i+1][j] = L[i+1][j][i][j] = x;
        }
        scanf( "%d%d%d%d", &sx, &sy, &tx, &ty );
        R[tx][ty] = R[1][1] = R[1][m] = R[n][1] = R[n][m] = 0;
        G[tx][ty] = G[1][1] = G[1][m] = G[n][1] = G[n][m] = inf - 100;
        printf( "Case #%d: %d\n", ++cas, solve() );
    }
    return 0;
}</span>


你可能感兴趣的:(HDU 4881 ZCC loves traffic lights 卡位最短路)