POJ1077 Eight(A* + 康托展开)
原题链接:http://poj.org/problem?id=1077
前置知识:康托展开 和 康托逆展开
解决什么问题?
能构造一个 的全排列 和 之间的双射,在解决全排列的哈希问题上有奇效。
康托展开即是将全排列映射到自然数,而康托逆展开则是将自然数映射到一个全排列。
怎么解决?
对于一个 项全排列 ,定义其康托展开就是其在所有 排列中的字典序的排名,再定义 表示除去 ,有多少个正整数小于 。那么显然,有计算式:
稍微解释一下,就是逐步确定总的排名,依次计算每一位对排名的贡献。对于第 位 ,比它小的可能性有 种,而每一种贡献了 个排名。
那么逆展开怎么做?也只要逆向逐步确定即可,并且根据康托展开的定义,我们能且仅能确定一个全排列。
具体实现如下:
int cantor(vector<int> p) {
int ret = 0;
for (int i = 0; i < 9; i++) {
int rnk = p[i]; for (int j = 0; j < i; j++) if (p[j] < p[i]) rnk--;
ret += (rnk - 1) * fac[8 - i];
}
return ret; // rank in [0, 9! - 1]
}
vector<int> cantorRev(int num) {
vector<int> ret, p; ret.resize(9); p.resize(9);
for (int i = 0; i < 9; i++) p[i] = i + 1;
for (int i = 8; i >= 0; i--) {
int rnk = num / fac[i] + 1;
for (int j = 0; j < 9; j++) if (p[j]) if (--rnk == 0) { ret[8 - i] = p[j]; p[j] = 0; break; }
num %= fac[i];
}
return ret;
}
补充一下,我的实现中康托展开的复杂度是 的,而我们显然能够使用树状数组快速统计出 ,时间复杂度就能降为 ,但是本题中 ,优化意义不大,因此没有写树状数组。同样地,康托逆展开也可以用平衡树()优化到 ,然而平衡树常数巨大,有可能产生逆优化。
这么做有什么好处?
相比线性哈希等做法,代码实现方便、美观,且这样节省空间,因为构造的是 的映射,在时间复杂度上其实并没有太大的优势。
道理我都懂,这题怎么做?
本题做法很多,有朴素 、双向 、 等等。
双向 是个很不错的做法,从 和 同时开始搜索,直到路径第一次有交即可。
本文主要使用启发式搜索算法 实现,定义的启发式估价函数是所有对应点对的曼哈顿距离之和,具体内容已经在《算法竞赛进阶指南》中讲述过,我在此不再赘述。
通过康托展开技巧,我们可以省去 等哈希方法,代码也更加清晰。
刚开始我使用的是 来操作,结果 飞了
展开查看 vector 版代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std;
#define mp make_pair
typedef pair<int, int> pii;
const int maxn = 500005;
const int inf = 0x3f3f3f3f;
int vis[maxn], dis[maxn], f[maxn];
int fac[10];
char dir[4] = {'u', 'd', 'l', 'r'};
int head[maxn], nxt[maxn << 2], tail[maxn << 2], type[maxn << 2], ecnt;
void init() {
fac[0] = 1;
for (int i = 1; i <= 9; i++) fac[i] = fac[i - 1] * i;
memset(head, 0, sizeof(head));
}
int cantor(vector<int> p) {
int ret = 0;
for (int i = 0; i < 9; i++) {
int rnk = p[i]; for (int j = 0; j < i; j++) if (p[j] < p[i]) rnk--;
ret += (rnk - 1) * fac[8 - i];
}
return ret; // rank in [0, 9! - 1]
}
vector<int> cantorRev(int num) {
vector<int> ret, p; ret.resize(9); p.resize(9);
for (int i = 0; i < 9; i++) p[i] = i + 1;
for (int i = 8; i >= 0; i--) {
int rnk = num / fac[i] + 1;
for (int j = 0; j < 9; j++) if (p[j]) if (--rnk == 0) { ret[8 - i] = p[j]; p[j] = 0; break; }
num %= fac[i];
}
return ret;
}
int mmp[5][5], mmpT[5][5];
int manhattan(int num) {
vector<int> p = cantorRev(num); int ret = 0;
for (int i = 0; i < 9; i++) mmp[(i / 3) + 1][(i % 3) + 1] = p[i];
for (int i = 0; i < 9; i++) mmpT[(i / 3) + 1][(i % 3) + 1] = i;
for (int i = 1; i <= 3; i++) for (int j = 1; j <= 3; j++)
for (int x = 1; x <= 3; x++) for (int y = 1; y <= 3; y++)
if (mmp[i][j] == mmpT[x][y]) ret += abs(i - x) + abs(j - y);
return ret;
}
void addedge(int u, int v, int t) {
nxt[++ecnt] = head[u];
head[u] = ecnt;
tail[ecnt] = v;
type[ecnt] = t;
}
void print(int num) {
vector<int> tmp = cantorRev(num);
for (int i = 0; i < 9; i++) cout << tmp[i] << " "; cout << endl;
}
pii last[maxn];
void Astar(int S, int T) {
memset(dis, inf, sizeof(dis));
memset(vis, 0, sizeof(vis));
priority_queue<pii> pq;
dis[S] = 0; pq.push(mp(-(0 + f[S]), S));
while (!pq.empty()) {
pii cur = pq.top(); pq.pop();
int u = cur.second;
if (vis[u]) continue; vis[u] = 1;
if (u == T) break;
for (int e = head[u]; e; e = nxt[e]) {
int v = tail[e];
if (dis[v] > dis[u] + 1) {
dis[v] = dis[u] + 1;
last[v] = mp(u, type[e]);
pq.push(mp(-(dis[v] + f[v]), v));
}
}
}
}
int main() {
init(); vector<int> p; p.resize(9);
for (int i = 0; i < 9; i++) {
char c[5]; scanf("%s", c);
if (c[0] == 'x') p[i] = 9;
else p[i] = c[0] - '0';
}
int S = cantor(p), T = 0;
for (int i = 0; i < fac[9]; i++) {
p = cantorRev(i); int pos = 0;
for (int j = 0; j < 9; j++) if (p[j] == 9) { pos = j; break; }
if (pos - 3 >= 0) { // U
swap(p[pos - 3], p[pos]);
addedge(i, cantor(p), 0);
swap(p[pos - 3], p[pos]);
}
if (pos + 3 < 9) { // D
swap(p[pos + 3], p[pos]);
addedge(i, cantor(p), 1);
swap(p[pos + 3], p[pos]);
}
if (pos > 0 && pos / 3 == (pos - 1) / 3) { // L
swap(p[pos - 1], p[pos]);
addedge(i, cantor(p), 2);
swap(p[pos - 1], p[pos]);
}
if (pos < 8 && pos / 3 == (pos + 1) / 3) { // R
swap(p[pos + 1], p[pos]);
addedge(i, cantor(p), 3);
swap(p[pos + 1], p[pos]);
}
}
for (int i = 0; i < fac[9]; i++) f[i] = manhattan(i);
Astar(S, T);
if (!vis[T]) puts("unsolvable");
else {
string ans;
for (int u = T; u != S; u = last[u].first) ans.push_back(dir[last[u].second]);
reverse(ans.begin(), ans.end());
printf("%s\n", ans.c_str());
}
return 0;
}
展开查看 AC 代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std;
#define mp make_pair
typedef pair<int, int> pii;
const int maxn = 500005;
const int inf = 0x3f3f3f3f;
int vis[maxn], dis[maxn], f[maxn];
int fac[10];
char dir[4] = {'u', 'd', 'l', 'r'};
int head[maxn], nxt[maxn << 2], tail[maxn << 2], type[maxn << 2], ecnt;
void init() {
fac[0] = 1;
for (int i = 1; i <= 9; i++) fac[i] = fac[i - 1] * i;
memset(head, 0, sizeof(head));
}
int cantor(int *p) {
int ret = 0;
for (int i = 0; i < 9; i++) {
int rnk = p[i]; for (int j = 0; j < i; j++) if (p[j] < p[i]) rnk--;
ret += (rnk - 1) * fac[8 - i];
}
return ret; // rank in [0, 9! - 1]
}
int mmp[5][5], mmpT[5][5];
int manhattan(int *p) {
int ret = 0;
for (int i = 0; i < 9; i++) mmp[(i / 3) + 1][(i % 3) + 1] = p[i];
for (int i = 0; i < 9; i++) mmpT[(i / 3) + 1][(i % 3) + 1] = i;
for (int i = 1; i <= 3; i++) for (int j = 1; j <= 3; j++)
for (int x = 1; x <= 3; x++) for (int y = 1; y <= 3; y++)
if (mmp[i][j] == mmpT[x][y]) ret += abs(i - x) + abs(j - y);
return ret;
}
void addedge(int u, int v, int t) {
nxt[++ecnt] = head[u];
head[u] = ecnt;
tail[ecnt] = v;
type[ecnt] = t;
}
pii last[maxn];
void Astar(int S, int T) {
memset(dis, inf, sizeof(dis));
memset(vis, 0, sizeof(vis));
priority_queue<pii> pq;
dis[S] = 0; pq.push(mp(-(0 + f[S]), S));
while (!pq.empty()) {
pii cur = pq.top(); pq.pop();
int u = cur.second;
if (vis[u]) continue; vis[u] = 1;
if (u == T) break;
for (int e = head[u]; e; e = nxt[e]) {
int v = tail[e];
if (dis[v] > dis[u] + 1) {
dis[v] = dis[u] + 1;
last[v] = mp(u, type[e]);
pq.push(mp(-(dis[v] + f[v]), v));
}
}
}
}
int main() {
init(); int p[9];
for (int i = 0; i < 9; i++) {
char c[5]; scanf("%s", c);
if (c[0] == 'x') p[i] = 9;
else p[i] = c[0] - '0';
}
int S = cantor(p), T = 0, id = 0;
for (int i = 0; i < 9; i++) p[i] = i + 1;
do {
int pos = 0;
for (int j = 0; j < 9; j++) if (p[j] == 9) { pos = j; break; }
if (pos - 3 >= 0) { // U
swap(p[pos - 3], p[pos]);
addedge(id, cantor(p), 0);
swap(p[pos - 3], p[pos]);
}
if (pos + 3 < 9) { // D
swap(p[pos + 3], p[pos]);
addedge(id, cantor(p), 1);
swap(p[pos + 3], p[pos]);
}
if (pos > 0 && pos / 3 == (pos - 1) / 3) { // L
swap(p[pos - 1], p[pos]);
addedge(id, cantor(p), 2);
swap(p[pos - 1], p[pos]);
}
if (pos < 8 && pos / 3 == (pos + 1) / 3) { // R
swap(p[pos + 1], p[pos]);
addedge(id, cantor(p), 3);
swap(p[pos + 1], p[pos]);
}
f[id] = manhattan(p);
id++;
} while (next_permutation(p, p + 9));
Astar(S, T);
if (!vis[T]) puts("unsolvable");
else {
string ans;
for (int u = T; u != S; u = last[u].first) ans.push_back(dir[last[u].second]);
reverse(ans.begin(), ans.end());
printf("%s\n", ans.c_str());
}
return 0;
}
反思与总结
我是上来就将整个图建好的,虽然改成了数组,但是运行速度还是堪忧。
看了网上的其它解法,好像只要边跑 边建图即可,因为图中大部分点都是访问不到的。
这题判无解有个基于逆序对的高级思想,我并没有使用,因为 完全跑的完,只要最后判一下终点有没有被访问过即可(其实是因为我不会证明这个结论,就不放上来献丑了)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话