1 条题解
-
0
题目 G. X 光环 详细题解
问题重述
有一个 行 列的网格,每个格子 有一个高度 。移动规则:从 可以走到相邻的四个格子(上下左右),代价为 ,其中 是一个奇数正整数()。注意代价可以为负。
给定 个查询,每个询问从起点 到终点 的最小总代价。若存在一条路径使得总代价可以任意小(即存在负环),则输出INVALID;否则输出最小总代价。
关键性质
由于 是奇数,函数 是奇函数:
因此,沿一条边正向和反向的代价互为相反数。这导致沿着任何环的代价具有反对称性。
零环条件
定义环的总代价为沿环走一圈的代价之和。若所有环的总代价均为 ,则称该网格是零环的。
引理 1:网格是零环的当且仅当每个 子网格(即由相邻两行两列构成的四个格子)的环代价为 。
证明:任意环都可以分解为若干个 基本环的线性组合。因此,若所有 环的代价为零,则所有环的代价为零;反之,若存在一个 环的代价非零,则它本身就是一个非零环。由于奇函数性质,该环的代价要么为正要么为负,取其反向即可得到负环。
因此,判断整个网格是否存在负环(即是否存在总代价可以任意小的路径)等价于检查是否存在一个 子网格,其四个边按顺时针方向的代价之和不为 。
无负环时的最短路径
若所有 子网格的环代价均为 ,则网格是零环的。此时,任意两点之间的路径代价与路径无关,只取决于起点和终点。更具体地,存在一个势能函数 ,使得从 到 的任意路径的代价等于 。这可以通过以下事实得出:
- 由零环条件,对任意三点 有 ,从而 且 。
- 固定一个参考点 (例如 ),定义 。则 。
因此,只需计算出从 到所有格子的代价 ,即可在 时间内回答每个查询:
计算 的方法
由于所有环代价为零,从 到任意格子的任意路径的代价都相同。因此,我们可以通过一次广度优先搜索(BFS)或深度优先搜索(DFS)来得到 。具体做法:
- 初始化 ,用一个队列存储已访问的格子。
- 从队列中取出格子 ,对它的四个邻居 ,若 未访问,则计算 并将 入队。
- 因为所有路径代价相等,第一次访问时得到的 就是正确的,后续不会再被更新。
整个遍历过程 ,因为每个格子只入队一次,每条边只被考虑一次。
算法步骤总结
- 读入 以及高度矩阵 。
- 检查所有 子网格(,),计算$$\text{cycle} = (H_{i,j}-H_{i,j+1})^X + (H_{i,j+1}-H_{i+1,j+1})^X + (H_{i+1,j+1}-H_{i+1,j})^X + (H_{i+1,j}-H_{i,j})^X $$若存在某个 ,则标记整个网格为无效(存在负环)。
- 若无效,则对于所有查询输出
INVALID。 - 否则,进行 BFS 从 出发计算 数组。
- 对于每个查询 ,输出 。
复杂度分析
- 检查 子网格:。
- BFS 遍历:。
- 每个查询:。
- 总时间复杂度 ,空间 。满足 , 的限制。
参考代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll pow_odd(ll base, int exp) { ll res = 1; for (int i = 0; i < exp; ++i) res *= base; return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); int R, C, X; cin >> R >> C >> X; vector<string> H(R); for (int i = 0; i < R; ++i) cin >> H[i]; // 检查所有 2x2 子网格是否零环 bool invalid = false; for (int i = 0; i < R - 1; ++i) { for (int j = 0; j < C - 1; ++j) { int a = H[i][j] - '0'; int b = H[i][j+1] - '0'; int c = H[i+1][j+1] - '0'; int d = H[i+1][j] - '0'; ll cycle = pow_odd(a - b, X) + pow_odd(b - c, X) + pow_odd(c - d, X) + pow_odd(d - a, X); if (cycle != 0) { invalid = true; break; } } if (invalid) break; } if (invalid) { int Q; cin >> Q; while (Q--) { int rs, cs, rf, cf; cin >> rs >> cs >> rf >> cf; cout << "INVALID\n"; } return 0; } // BFS 计算势能 vector<vector<ll>> phi(R, vector<ll>(C, 0)); vector<vector<bool>> vis(R, vector<bool>(C, false)); queue<pair<int,int>> q; q.push({0,0}); vis[0][0] = true; phi[0][0] = 0; int dr[] = {1, -1, 0, 0}; int dc[] = {0, 0, 1, -1}; while (!q.empty()) { auto [r,c] = q.front(); q.pop(); int h1 = H[r][c] - '0'; for (int d = 0; d < 4; ++d) { int nr = r + dr[d], nc = c + dc[d]; if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue; if (vis[nr][nc]) continue; int h2 = H[nr][nc] - '0'; phi[nr][nc] = phi[r][c] + pow_odd(h1 - h2, X); vis[nr][nc] = true; q.push({nr, nc}); } } int Q; cin >> Q; while (Q--) { int rs, cs, rf, cf; cin >> rs >> cs >> rf >> cf; --rs; --cs; --rf; --cf; cout << phi[rf][cf] - phi[rs][cs] << '\n'; } return 0; }
样例验证
以样例1为例,,所有 子网格的环代价均为 ,计算势能后得到查询结果与输出一致。
样例2中存在非零 子网格,因此所有查询输出INVALID。
样例3中 ,同样无负环,输出正确。本题的关键在于识别零环条件与势能函数的线性性质,从而将复杂的图论问题简化为简单的网格遍历。
- 1
信息
- ID
- 7167
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者