主要讲讲从 5 分到 100 分的变化。
时间 | 状态 | 分数 |
---|---|---|
07-19 14:37:47 | Accepted | 100 |
07-19 14:36:45 | Unaccepted | 25 |
07-19 14:31:18 | Unaccepted | 55 |
07-18 19:48:59 | Accepted | 100 |
07-18 16:46:50 | Unaccepted | 50 |
07-18 16:44:54 | Unaccepted | 50 |
07-18 14:56:24 | Unaccepted | 50 |
07-18 14:50:11 | Unaccepted | 50 |
07-18 14:43:57 | Unaccepted | 15 |
07-18 14:42:40 | Unaccepted | 15 |
07-18 14:32:01 | Unaccepted | 15 |
07-18 14:30:21 | Unaccepted | 5 |
07-18 14:25:37 | Unaccepted | 5 |
有一个
任何一个时刻,所站在位置必须有颜色(不能无色),只能向上、 下、左、 右四个方向前进。从一个格子走向另一个格子时,如果两个格子的颜色相同,那花费为 (0, 0)
到右下角 (m-1, m-1)
最小花费。保证左上角有颜色。
一种思路是,把棋盘转化为一个图,求一遍最短路。其中图的节点是有色格子。
#include <iostream>
#include <queue>
#define add(u, v, w) (eg[tot] = {v, hd[u], w}, hd[u] = tot++)
const int M = 102, N = 1003, dx[]{-1, 1, 0, 0}, dy[]{0, 0, -1, 1};
int m, n, mp[M][M], ans = -1, hd[N], tot;
struct edge { int to, nx, wg; } eg[N * 8];
using pii = std::pair<int, int>;
std::priority_queue<pii, std::vector<pii>, std::greater<pii>> pq; // [dis_u, u]
int dis[N]; bool used[N];
int main() {
std::cin >> m >> n;
while (n--) {
int x, y, c;
std::cin >> x >> y >> c;
mp[x - 1][y - 1] = c + 1;
}
for (int i = 0; i < m * m; i++) hd[i] = -1;
for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) if (mp[i][j]) {
for (int k = 0; k < 4; k++) {
int ii = i + dx[k], jj = j + dy[k];
if (0 > ii || ii >= m || 0 > jj || jj >= m) continue;
if (mp[ii][jj]) add(i * m + j, ii * m + jj, mp[i][j] != mp[ii][jj]);
else for (int kk = 0, i3, j3; kk < 4; kk++) if (kk ^ k ^ 1) {
i3 = ii + dx[kk], j3 = jj + dy[kk];
if (mp[i3][j3]) add(i * m + j, i3 * m + j3, 2 + (mp[i][j] != mp[i3][j3]));
}
}
}
for (int i = 0; i < m * m; i++) dis[i] = 0x7fffffff, pq.push({dis[i], i});
dis[0] = 0;
while (!pq.empty()) {
int u = pq.top().second; pq.pop();
if (used[u]) continue;
for (int p = hd[u]; ~p; p = eg[p].nx) {
int t = dis[u] + eg[p].wg;
if (t < dis[eg[p].to]) dis[eg[p].to] = t;
}
}
std::cout << (dis[m * m - 1] == 0x7fffffff ? -1 : dis[m * m - 1]) << '\n';
}
11WA,1TLE,7RE,5 分。
for (int i = 0; i < m * m; i++) dis[i] = 0x7fffffff, pq.push({dis[i], i});
dis[0] = 0;
while (!pq.empty()) {
int u = pq.top().second; pq.pop();
if (used[u]) continue;
for (int p = hd[u]; ~p; p = eg[p].nx) {
int t = dis[u] + eg[p].wg;
if (t < dis[eg[p].to]) dis[eg[p].to] = t;
}
}
注意这段 Dijkstra,少了标记 used[u],所以我加上 used[u]=true
后提交了。
if (used[u]) continue;
+ used[u] = true;
for (int p = hd[u]; ~p; p = eg[p].nx) {
成绩没变。
松弛操作里,没有更新最短边队列。我加上更新后又交了。
if (t < dis[eg[p].to]) dis[eg[p].to] = t, pq.push({t, eg[p].to});
++++++++++++++++++++++++
10WA(-1), 17RE,0TLE(-1),15(+10) 分。
for (int i = 0; i < m * m; i++) dis[i] = 0x7fffffff, pq.push({dis[i], i});
dis[0] = 0;
这是 Dijkstra 的初始化代码。dis
初始化没问题,但是队列初始化后都是 inf, 而不是 {(0, 0), (inf, 1), ..., (inf, n-1)}
,这不是我想要的。事实上,初始化时队列并不需要添加距离为无限大的点,因为即使把距离为无限大的点拿出来,也不能给其它点松弛。我修改了初始化,又提交了。
for (int i = 1; i < m * m; i++) dis[i] = 0x7fffffff, pq.push({dis[i], i});
~
dis[0] = 0, pq.push({0, 0});
+++++++++++++++++
成绩一样。
这是存点相关数组的代码。
const int M = 102, N = 1003;
int dis[N]; bool used[N];
按照我建图的方法,似乎只有 (i, j)
的格子映射为编号为 i*m+j
的点。这意味着,点的编号最大为
int dis[M*M]; bool used[M*M];
~~~ ~~~
10WA,2RE(-15),3TLE(+3),15 分。
这次改了两处!
int m, n, mp[M][M], hd[N], tot;
和上一个一样的漏洞,链式前向星的 hd
数组没有修改。
int m, n, mp[M][M], hd[M * M], tot;
~~~~~
else for (int kk = 0, i3, j3; kk < 4; kk++) if (kk ^ k ^ 1) {
i3 = ii + dx[kk], j3 = jj + dy[kk];
if (mp[i3][j3]) add(i * m + j, i3 * m + j3, 2 + (mp[i][j] != mp[i3][j3]));
}
建图的操作是,从有色格子遍历 4 个方向(k
),如果到达有色格子就建边;如果到了无色格子就再往四个方向(kk
)再走一步,如果方向不相反(kk^k^1
)就尝试建边。漏洞在于没有越界判断。
else for (int kk = 0, i3, j3; kk < 4; kk++) if (kk ^ k ^ 1) {
i3 = ii + dx[kk], j3 = jj + dy[kk];
if (0 > i3 || i3 >= m || 0 > j3 || j3 >= m || !mp[i3][j3]) continue;
+++++++++++++++++++++++++++++++++++++++++++ +++++++++
add(i * m + j, i3 * m + j3, 2 + (mp[i][j] != mp[i3][j3]));
}
10WA,0RE(-2),0TLE(-3),50 分,“错了一火车”。
又回到松弛代码。
for (int p = hd[u]; ~p; p = eg[p].nx) {
int t = dis[u] + eg[p].wg;
if (t < dis[eg[p].to]) dis[eg[p].to] = t, pq.push({t, eg[p].to});
}
如果 dis[u]
是无限大怎么办?如果继续的硬算的话就会溢出(即使取的 inf
不会立即溢出,经过足够多次累加后仍会溢出)。一种解决方法是,遇到 dis[u]
为无限大时退出算法(因为此时队列里剩余的全是无限大,无限大都不能松弛);另一种方法是初始化时不让无限大进队。
for (int p = hd[u]; ~p; p = eg[p].nx) {
int t = dis[u] + eg[p].wg;
+ if (dis[u] == 0x7fffffff) t = 0x7fffffff;
if (t < dis[eg[p].to]) dis[eg[p].to] = t, pq.push({t, eg[p].to});
}
成绩一样。
把代码重新写了一遍,没发现其它的漏洞。
#include <cstdio>
#include <queue>
const int M = 102, N = 1003, INF = 0x7fffffff;
int m, n, mp[M][M];
struct edge { int to, nx, wg; } eg[N * 8];
int hd[M * M], tot;
#define add(u, v, w) (eg[tot] = {v, hd[u], w}, hd[u] = tot++)
using pii = std::pair<int, int>;
std::priority_queue<pii, std::vector<pii>, std::greater<pii>> pq;
int dis[M * M]; bool used[M * M];
void read() {
int r, c, col;
scanf("%d%d", &m, &n);
while (n--) {
scanf("%d%d%d", &r, &c, &col);
mp[r - 1][c - 1] = col + 1;
}
}
void build() {
for (int i = 0; i < m * m; i++) hd[i] = -1;
const int dr[]{-1, 1, 0, 0}, dc[]{0, 0, -1, 1};
int rr, cc, r3, c3;
for (int r = 0; r < m; r++) for (int c = 0; c < m; c++)
if (mp[r][c]) for (int i = 0; i < 4; i++) {
rr = r + dr[i], cc = c + dc[i];
if (0 > rr || rr >= m || 0 > cc || cc >= m) continue;
if (mp[rr][cc]) add(r * m + c, rr * m + cc, mp[r][c] != mp[rr][cc]);
else for (int j = 0; j < 4; j++) if (i ^ j ^ 1) {
r3 = rr + dr[j], c3 = cc + dc[j];
if (0 > r3 || r3 >= m || 0 > c3 || c3 >= m || !mp[r3][c3]) continue;
add(r * m + c, r3 * m + c3, 2 + (mp[r][c] != mp[r3][c3]));
}
}
}
void dijk() {
dis[0] = 0, pq.push({0, 0});
for (int i = 1; i < m * m; i++) dis[i] = INF, pq.push({INF, i});
while (!pq.empty()) {
int u = pq.top().second; pq.pop();
if (used[u]) continue;
used[u] = true;
if (dis[u] != INF) for (int p = hd[u]; ~p; p = eg[p].nx) {
int d = dis[u] + eg[p].wg;
if (dis[eg[p].to] > d) dis[eg[p].to] = d, pq.push({d, eg[p].to});
}
}
}
int main() {
read(), build(), dijk();
printf("%d\n", dis[m * m - 1] == INF ? -1 : dis[m * m - 1]);
}
成绩一样。
if (dis[eg[p].to] > d) dis[eg[p].to] = d, pq.push({d, eg[p].to});
加了没用的一条语句,成绩一样。也就是入队前判断是否未使用过。事实是,已经使用(标记)过的点已经找到了最短路,不会再更新了。
if (dis[eg[p].to] > d) {
dis[eg[p].to] = d;
if (!used[eg[p].to]) pq.push({d, eg[p].to});
++++++++++++++++++++
}
对着 DFS 题解抄了一遍,AC 了。这次提交不计入成绩差分。
#include <cstdio>
const int N = 102, inf = 0x7fffffff, dx[]{-1, 1, 0, 0}, dy[]{0, 0, -1, 1};
int n, a[N][N], dis[N][N], ans = inf;
bool vis[N][N];
void read() {
int m, x, y, c;
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &x, &y, &c);
a[x - 1][y - 1] = c + 1;
}
}
void dfs(int x, int y, int ost, int lur) {
if (x + 1 == n && y + 1 == n) {
if (ost < ans) ans = ost;
return;
}
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
if (0 > xx || xx >= n || 0 > yy || yy >= n || vis[xx][yy]
|| !a[x][y] && !a[xx][yy]) continue;
if (a[xx][yy]) {
if (lur == a[xx][yy] && ost < dis[xx][yy]) {
vis[xx][yy] = true, dis[xx][yy] = ost;
dfs(xx, yy, ost, lur);
vis[xx][yy] = false;
} else if (ost + 1 < ans && ost + 1 < dis[xx][yy]) {
vis[xx][yy] = true, dis[xx][yy] = ost + 1;
dfs(xx, yy, ost + 1, a[xx][yy]);
vis[xx][yy] = false;
}
}
else if (ost + 2 < dis[xx][yy]) {
vis[xx][yy] = true, dis[xx][yy] = ost + 2;
dfs(xx, yy, ost + 2, lur);
vis[xx][yy] = false;
}
}
}
int main() {
read();
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) dis[i][j] = inf;
vis[0][0] = true; dfs(0, 0, 0, a[0][0]);
printf("%d\n", ans == inf ? -1 : ans);
}
7 月 19 日中午,我下载一个数据点,发现了漏洞。之前我们建的边两端都必须有颜色嘛,但题目保证起点有颜色,但不保证终点有颜色。如果终点没颜色,那么我的代码就搜索不到路径。实际上,走到终点后搜索就能结束了,不用管别的什么。所以建图时特判是否到达终点即可。但我脑子抽风了,把特判放在走两步(而不是走一步后)。
if (0 > r3 || r3 >= m || 0 > c3 || c3 >= m || !mp[r3][c3]) continue;
if (0 > r3 || r3 >= m || 0 > c3 || c3 >= m) continue;
if (!mp[r3][c3]) {
+ if (r3 + 1 == m && c3 + 1 == m) add(r * m + c, m * m - 1, 2);
+ else continue;
}
9WA(-1),55 分。
我提交后发现特判塞错了,所以我改过来又交了一次。
else for (int j = 0; j < 4; j++) if (i ^ j ^ 1) {
r3 = rr + dr[j], c3 = cc + dc[j];
if (0 > r3 || r3 >= m || 0 > c3 || c3 >= m) continue;
- if (!mp[r3][c3]) {
- if (r3 + 1 == m && c3 + 1 == m) add(r * m + c, m * m - 1, 2);
- else continue;
- }
add(r * m + c, r3 * m + c3, 2 + (mp[r][c] != mp[r3][c3]));
}
+ else if (rr + 1 == m || cc + 1 == m) add(r * m + c, m * m - 1, 2);
else for (int j = 0; j < 4; j++) if (i ^ j ^ 1) {
r3 = rr + dr[j], c3 = cc + dc[j];
if (0 > r3 || r3 >= m || 0 > c3 || c3 >= m || !mp[r3][c3]) continue;
add(r * m + c, r3 * m + c3, 2 + (mp[r][c] != mp[r3][c3]));
}
15WA(+6),25 分。
else if (rr + 1 == m || cc + 1 == m) add(r * m + c, m * m - 1, 2);
但我还是写错了。改过来提交,终于 AC 了。
else if (rr + 1 == m && cc + 1 == m) add(r * m + c, m * m - 1, 2);
~~
这道题目综合了我的常见错误,包含了模板、越界判断、数组、算法设计。
我在这里总结我自己的 Dijkstra 模板:
const int N = ..., M = ..., inf = 0x7fffffff;
using pii = std::pair<int, int>;
std::priority_queue<pii, std::vector<pii>, std::greater<pii>> pq;
int hd[N], to[M], nx[M], wg[M], ds[N], s;
bool ok[N];
for (int i = 0; i < n; i++) ds[i] = inf, ok[i] = false;
ds[s] = 0; pq.push({0, s});
while (!pq.empty()) {
int u = pq.top().second; pq.pop();
if (ok[u]) continue;
ok[u] = true;
for (int p = hd[u]; ~p; p = nx[p]) {
int v = to[p], k = ds[u] + wg[p];
if (ds[v] > k) ds[v] = k, pq.push({k, v});
}
}
搜索时一定不能忘记的越界判断:
if (0 > rr || rr >= n || 0 > cc || cc >= m || vis[rr][cc]) continue;
估计点数量时不能漏掉没有边的点。另外,写 Johnson 最短路时一定不能写错边数量是
开始时看到“保证左上角有颜色”时就应该联想到“不保证右下角有颜色”。用 DFS 没错的原因是,“踩着的格子原本有没有颜色”被编码进了参数 lur
,从而保证数据能到达终点时,程序能搜到终点。