![]()

夺宝奇兵:某国国宝失窃,你千辛万苦追回珍宝,正准备上交给国家时,却被怀疑是赝品。为了鉴别真伪,需要将宝石放到迷宫的魔法阵中。但迷宫中有特殊机关,不允许特定的移动模式。如下图的移动方式是错误的
而下图才是正确的打开方式
给定地图和禁止模式,求抵达魔法阵所需最少步数。
输入数据第一行是地图的行数N和列数M。
接着是H行W列字符地图,字符意义如下:
S 入口
G 魔法阵
. 通道
# 墙壁
接着一个整数P代表禁止移动模式的个数,然后是P行模式串,其组成字符意义如下:
UDLR分别代表上下左右。
输出一个整数,代表最低步数。如果无法抵达魔法阵,则输出-1。
4.7华丽地处理字符串
动态规划算法
谈起字符串算法,应该算是我的老本行了。
主框架是BFS,在普通的BFS中,搜索状态用二维数组bool visited[x][y]记录。在这里当前搜索状态一共有两个要素,一是当前坐标,二是当前字符串。用模式串构造trie树,则当前字符串等效为trie树的某个节点。于是可以按如下方式记录访问状态:
set<Node *> visited[50][50];
在遍历的过程中,如何判断当前路径中是否含有非法模式串呢?多模式匹配就上AC自动机了。也可以用后缀trie树,但本质上AC自动机就是前缀trie树与后缀trie树的结合体。由于AC自动机的模板更好找,就懒得写后缀树了。
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <string>
#include <set>
using namespace std;
#define INF 0x3f3f3f3f
// Aho Corasick
struct Node
{
map<char, Node *> next;
Node *fail;
vector<int> match;
Node() : fail(NULL)
{}
};
Node *build(vector<string> pattens)
{
// 1. 构建trie树
Node *root = new Node();
root->fail = root;
for (int i = 0; i < pattens.size(); i++)
{
Node *p = root;
for (auto c : pattens[i])
{
if (p->next[c] == 0)
p->next[c] = new Node();
p = p->next[c];
}
p->match.push_back(i);
}
// 2. failure表
queue<Node *> que;
for (int i = 0; i < 128; i++)
{
if (!root->next[i])
{
root->next[i] = root;
}
else
{
root->next[i]->fail = root;
que.push(root->next[i]);
}
}
while (!que.empty())
{
Node *p = que.front();
que.pop();
for (auto a : p->next)
{
int i = a.first;
Node *np = a.second;
if (!np)
continue;
// add que
que.push(np);
// search failure link
Node *f = p->fail;
while (!f->next[i])
f = f->fail;
np->fail = f->next[i];
// update matching list
np->match.insert(np->match.end(), np->fail->match.begin(), np->fail->match.end());
}
}
return root;
}
// Trie树节点 p 接受字符 c 转移
Node *next_node(Node *p, char c)
{
while (!p->next[c])
p = p->fail;
return p->next[c];
}
Node *next_node(Node *p, string query)
{
for (char c : query)
{
p = next_node(p, c);
}
return p;
}
class State
{
public:
int x;
int y;
int cost;
string path;
State(int x, int y, int cost, const string &path) : x(x), y(y), cost(cost), path(path)
{}
bool operator<(const State &s) const
{
return cost < s.cost;
}
bool operator>(const State &s) const
{
return cost > s.cost;
}
};
int tx[] = {0, 1, 0, -1}; //URDL
int ty[] = {-1, 0, 1, 0};
const static string dir_str[4] = {"U", "R", "D", "L"};
int main()
{
int H, W;
while (~scanf("%d %d", &H, &W), H)
{
char stage[50][50];
set<Node *> visited[50][50];
int sx = 0;
int sy = 0;
int gx = 0;
int gy = 0;
for (int y = 0; y < H; y++)
{
char line[51];
scanf("%s", line);
for (int x = 0; x < W; x++)
{
stage[y][x] = line[x];
if (line[x] == 'S')
{
sx = x;
sy = y;
}
else if (line[x] == 'G')
{
gx = x;
gy = y;
}
}
}
int total_prohibited_sequences;
scanf("%d", &total_prohibited_sequences);
vector<string> keywords(total_prohibited_sequences);
for (int sequence_idx = 0; sequence_idx < total_prohibited_sequences; sequence_idx++)
{
cin >> keywords[sequence_idx];
}
Node *root = build(keywords);
// BFS
priority_queue<State, vector<State>, greater<State> > que;
que.push(State(sx, sy, 0, ""));
int ans = INF;
while (!que.empty())
{
State s = que.top();
que.pop();
string route = s.path;
int cost = s.cost;
int sx = s.x;
int sy = s.y;
for (int i = 0; i < 4; i++)
{
int dx = sx + tx[i];
int dy = sy + ty[i];
if (dx < 0 || dx >= W || dy < 0 || dy >= H)
continue;
if (stage[dy][dx] == '#')
continue;
string next = route + dir_str[i];
if (next.size() > 10) // 剪枝策略,题目限定禁止模式的长度不超过10
next = next.substr(next.size() - 10);
auto p = next_node(root, next);
if (p->match.size())
{
continue;
}
if (visited[dy][dx].count(p)) // 已经访问过这个状态
continue;
visited[dy][dx].insert(p);
if (stage[dy][dx] == 'G')
{
ans = cost + 1;
goto found;
}
que.push(State(dx, dy, cost + 1, next));
}
}
found:;
printf("%d\n", ans == INF ? -1 : ans);
}
}

恭喜,锦旗一面和500软妹币已发货
知识共享署名-非商业性使用-相同方式共享:码农场 » AOJ 2212 Stolen Jewel 题解《挑战程序设计竞赛》
码农场
