【BZOJ3205_洛谷3638】[APIO2013]機器人(動態規劃)
題目:
分析:
卡了一天的神題……(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\)
考慮反證。假設之前第二個佇列是有序的,在某一次更新後變得無序了。設用於更新的點是\(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();
}