POJ1077 Eight(A* + 康托展开)
作者:互联网
前置知识:康托展开 和 康托逆展开
解决什么问题?
能构造一个 \(1\sim N\) 的全排列 和 \(0\sim N!-1\) 之间的双射,在解决全排列的哈希问题上有奇效。
康托展开即是将全排列映射到自然数,而康托逆展开则是将自然数映射到一个全排列。
怎么解决?
对于一个 \(N\) 项全排列 \(\{p\}\),定义其康托展开就是其在所有 \(1\sim N\) 排列中的字典序的排名,再定义 \(rank[i]\) 表示除去 \(p_1 \sim p_{i-1}\),有多少个正整数小于 \(p_i\)。那么显然,有计算式:
\[Cantor(p) = \sum_{i = 1}^N rank[i] \times (N-i)! \]稍微解释一下,就是逐步确定总的排名,依次计算每一位对排名的贡献。对于第 \(i\) 位 \(p_i\),比它小的可能性有 \(rank[i]\) 种,而每一种贡献了 \((N-i)!\) 个排名。
那么逆展开怎么做?也只要逆向逐步确定即可,并且根据康托展开的定义,我们能且仅能确定一个全排列。
具体实现如下:
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;
}
补充一下,我的实现中康托展开的复杂度是 \(O(N^2)\) 的,而我们显然能够使用树状数组快速统计出 \(rank[i]\),时间复杂度就能降为 \(O(N\log N)\),但是本题中 \(N=9\),优化意义不大,因此没有写树状数组。
这么做有什么好处?
相比线性哈希等做法,代码实现方便、美观,且这样节省空间,因为构造的是 \(0 \sim N!-1\) 的映射,在时间复杂度上其实并没有太大的优势。
道理我都懂,这题怎么做?
本题做法很多,有朴素 \(bfs\)、双向 \(bfs\)、\(A*\) 等等。
双向 \(bfs\) 是个很不错的做法,从 \(S\) 和 \(T\) 同时开始搜索,直到路径第一次有交即可。
本文主要使用启发式搜索算法 \(A*\) 实现,定义的启发式估价函数是所有对应点对的曼哈顿距离之和,具体内容已经在《算法竞赛进阶指南》中讲述过,我在此不再赘述。
通过康托展开技巧,我们可以省去 \(map\) 等哈希方法,代码也更加清晰。
刚开始我使用的是 \(vector\) 来操作,结果 \(T\) 飞了
展开查看 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;
}
反思与总结
我是上来就将整个图建好的,虽然改成了数组,但是运行速度还是堪忧。
看了网上的其它解法,好像只要边跑 \(A*\) 边建图即可,因为图中大部分点都是访问不到的。
这题判无解有个基于逆序对的高级思想,我并没有使用,因为 \(A*\) 完全跑的完,只要最后判一下终点有没有被访问过即可(其实是因为我不会证明这个结论,就不放上来献丑了)。
标签:int,POJ1077,pos,ret,++,Eight,swap,include,康托 来源: https://www.cnblogs.com/alfayoung/p/16152405.html