1. 程式人生 > >【BZOJ3205_洛谷3638】[APIO2013]機器人(動態規劃)

【BZOJ3205_洛谷3638】[APIO2013]機器人(動態規劃)

題目:

洛谷3638

分析:

卡了一天的神題……(OrzJumpmelon)

首先預處理出從點\(p\)\(d\)方向出發最終能到達的點\(nxt[p][d]\)。這個可以直接記憶化搜尋解決。如果出現環說明不能向這個方向出發,設為\(-1\)

struct point
{
    int x, y;
    point(const int _x = 0, const int _y = 0)
        : x(_x), y(_y) {}
};
inline bool check(const point &p)
{
    return p.x >= 0 && p.x < h && p.y >= 0 && p.y < w;
}
inline int ptoi(const point &p)
{
    return p.x * w + p.y;
}
bool vis[P][DIR], insta[P][DIR];
int dfs(const point &u, const int &d)
{
    int id = ptoi(u);
    if (vis[id][d])
        return nxt[id][d];
    if (insta[id][d])
    {
        vis[id][d] = true;
        return nxt[id][d] = -1;
    }
    int dd = d;
    if (map[id] == LEFT)
        dd = (dd + 1) & 3;
    if (map[id] == RIGHT)
        dd = (dd + 3) & 3;
    point v = point(u.x + dx[dd], u.y + dy[dd]);
    if (!check(v) || map[ptoi(v)] == WALL)
    {
        vis[id][d] = true;
        return nxt[id][d] = id;
    }
    else
    {
        insta[id][d] = true;
        nxt[id][d] = dfs(v, dd);
        insta[id][d] = false;
        vis[id][d] = true;
        return nxt[id][d];
    }
}

然後考慮用\(dp[i][j][u]\)表示令編號為\([i,j]\)的複合機器人在\(u\)點的最少步數,最終答案就是\(min(dp[0][n-1][u])\)。有兩種轉移方式:

1.把\([i,k]\)\((k,j]\)兩個機器人在\(p\)點拼起來,即:

\[dp[i][j][u]=min(dp[i][k][u]+dp[k+1][j][u])\]

2.把\([i,j]\)機器人推到\(u\)點,即(其中\(v\)能一步走到\(u\)即存在滿足\(nxt[v][d]=u\)\(d\)):

\[dp[i][j][u]=dp[i][j][v]+1\]

第一種直接區間DP即可。第二種存在迴圈更新,但是長得很像最短路……

於是我碼了Dijkstra,卡常卡到死也沒卡過去,還藉此機會跟Jumpmelon諞了一天qwq

下面介紹一下Jumpmelon給我講的優化:開兩個佇列,第一個佇列是一開始的點,按照\(dis\)排好序(\(dis[u]\)表示\(dp[i][j][u]\),下同);第二個佇列是已經更新,等待用來更新別的點的佇列,初始為空。每次將兩個佇列隊首中\(dis\)較小的一個取出來(相當於Dijkstra的堆頂)來鬆弛其他點,這樣複雜度是\(O(n+m)\)的,成功去掉堆的\(\log m\)。為什麼這樣是對的呢?

注意到所有邊權都是\(1\),於是點\(v\)被更新時第二個佇列的隊尾(如果存在)的\(dis\)

一定不會大於\(dis[v]\),所以直接把\(v\)插到隊尾不會影響第二個佇列的有序性。原因如下。

考慮反證。假設之前第二個佇列是有序的,在某一次更新後變得無序了。設用於更新的點是\(u\),被更新的點是\(v\),更新前第二個佇列的隊尾為\(t\),滿足\(dis[v]=dis[u]+1\)\(dis[t]>dis[v]\)。由於邊權都是\(1\),那麼曾用於更新\(t\)的點\(p\)滿足\(dis[p]=dis[t]-1\geq dis[v]>dis[u]\)。既然\(dis[u]<dis[p]\),由於之前兩個佇列都是有序的,且每次是取出兩佇列隊首的較小值更新,那麼\(u\)顯然應該在\(p\)之前被取出,矛盾。所以這種情況不存在。既然能保持兩個佇列的有序性,那麼就保證了每次取出\(dis\)最小的點,也就不需要堆了。(沒上文化課,表達能力為\(0\),自己都覺得佶屈聱牙跟個T嘴z子z一樣。感性理解就好)。

程式碼:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
#include <functional>
#include <vector>
#undef i
#undef j
#undef k
#undef true
#undef false
#undef min
#undef max
#undef sort
#undef swap
#undef if
#undef while
#undef for
#undef printf
#undef scanf
#undef putchar
#undef getchar
#define _ 0
using namespace std;

namespace zyt
{
    template<typename T>
    inline bool read(T &x)
    {
        char c;
        bool f = false;
        x = 0;
        do
            c = getchar();
        while (c != EOF && c != '-' && !isdigit(c));
        if (c == EOF)
            return false;
        if (c == '-')
            f = true, c = getchar();
        do
            x = x * 10 + c - '0', c = getchar();
        while (isdigit(c));
        if (f)
            x = -x;
        return true;
    }
    inline bool read(char &c)
    {
        do
            c = getchar();
        while (c != EOF && !isgraph(c));
        return c != EOF;
    }
    template<typename T>
    inline void write(T x)
    {
        static char buf[20];
        char *pos = buf;
        if (x < 0)
            putchar('-'), x = -x;
        do
            *pos++ = x % 10 + '0';
        while (x /= 10);
        while (pos > buf)
            putchar(*--pos);
    }
    const int W = 5e2, P = W * W, N = 9, DIR = 4, INF = 0x3f3f3f3f, B = 18;
    const int U = 0, L = 1, D = 2, R = 3, WALL = N, LEFT = N + 1, RIGHT = N + 2, BLANK = N + 3;
    const int dx[DIR] = {-1, 0, 1, 0};
    const int dy[DIR] = {0, -1, 0, 1};
    int n, w, h, p, nxt[P][DIR], map[P], dp[N][N][P];
    struct point
    {
        int x, y;
        point(const int _x = 0, const int _y = 0)
            : x(_x), y(_y) {}
    };
    inline bool check(const point &p)
    {
        return p.x >= 0 && p.x < h && p.y >= 0 && p.y < w;
    }
    inline int ptoi(const point &p)
    {
        return p.x * w + p.y;
    }
    bool vis[P][DIR], insta[P][DIR];
    int dfs(const point &u, const int &d)
    {
        int id = ptoi(u);
        if (vis[id][d])
            return nxt[id][d];
        if (insta[id][d])
        {
            vis[id][d] = true;
            return nxt[id][d] = -1;
        }
        int dd = d;
        if (map[id] == LEFT)
            dd = (dd + 1) & 3;
        if (map[id] == RIGHT)
            dd = (dd + 3) & 3;
        point v = point(u.x + dx[dd], u.y + dy[dd]);
        if (!check(v) || map[ptoi(v)] == WALL)
        {
            vis[id][d] = true;
            return nxt[id][d] = id;
        }
        else
        {
            insta[id][d] = true;
            nxt[id][d] = dfs(v, dd);
            insta[id][d] = false;
            vis[id][d] = true;
            return nxt[id][d];
        }
    }
    void Shortest_Path(const int l, const int r)
    {
        typedef pair<int, int> pii;
        static pii tmp[P];
        static bool vis[P];
        static queue<int> q1, q2;
        while (!q1.empty())
            q1.pop();
        while (!q2.empty())
            q2.pop();
        memset(vis, 0, sizeof(bool[p]));
        int *dis = dp[l][r], cnt = 0;
        for (int i = 0; i < p; i++)
            if (map[i] != WALL && dis[i] < INF)
                tmp[cnt++] = make_pair(dis[i], i), vis[i] = true;
        sort(tmp, tmp + cnt);
        for (int i = 0; i < cnt; i++)
            q1.push(tmp[i].second);
        while (!q1.empty() || !q2.empty())
        {
            int u = ((q1.empty() || (!q2.empty() && dis[q1.front()] > dis[q2.front()])) ? q2.front() : q1.front());
            if (!q1.empty() && u == q1.front())
                q1.pop();
            else
                q2.pop();
            vis[u] = false;
            for (int i = 0; i < DIR; i++)
            {
                int v = nxt[u][i];
                if (~v && dis[v] > dis[u] + 1)
                {
                    dis[v] = dis[u] + 1;
                    if (!vis[v])
                        q2.push(v), vis[v] = true;
                }
            }
        }
    }
    int work()
    {
        read(n), read(w), read(h);
        p = w * h;
        for (int i = 0; i < n; i++)
            for (int j = i; j < n; j++)
                memset(dp[i][j], INF, sizeof(int[p]));
        for (int i = 0; i < p; i++)
        {
            char c;
            read(c);
            if (isdigit(c))
            {
                map[i] = c - '1';
                dp[map[i]][map[i]][i] = 0;
            }
            else if (c == '.')
                map[i] = BLANK;
            else if (c == 'x')
                map[i] = WALL;
            else if (c == 'A')
                map[i] = LEFT;
            else
                map[i] = RIGHT;
            }
        for (int i = 0; i < h; i++)
            for (int j = 0; j < w; j++)
            {
                int id = ptoi(point(i, j));
                if (map[id] != WALL)
                    for (int d = 0; d < DIR; d++)
                        nxt[id][d] = dfs(point(i, j), d);
            }
        for (int l = 1; l <= n; l++)
            for (int i = 0; i + l - 1 < n; i++)
            {
                int j = i + l - 1;
                for (int u = 0; u < p; u++)
                    if (map[u] != WALL)
                        for (int k = i; k < j; k++)
                            dp[i][j][u] = min(dp[i][j][u], dp[i][k][u] + dp[k + 1][j][u]);
                Shortest_Path(i, j);
            }
        int ans = INF;
        for (int i = 0; i < p; i++)
            ans = min(ans, dp[0][n - 1][i]);
        if (ans == INF)
            write(-1);
        else
            write(ans);
        return (0^_^0);
    }
}
int main()
{
    return zyt::work();
}