搜索
最短路模型
迷宫问题
其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
#include <iostream>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int g[N][N];
bool st[N][N];
PII path[N][N];
int n;
void bfs(int x, int y) {
queue<PII> q;
q.push({x, y});
st[x][y] = true;
int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
while (q.size()) {
PII t = q.front(); q.pop();
int x = t.first, y = t.second;
for (int i = 0; i < 4; ++i) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < n && b >= 0 && b < n && g[a][b] == 0 && !st[a][b]) {
st[a][b] = true;
path[a][b] = {x, y};
q.push({a, b});
}
}
}
}
int main(void) {
cin >> n;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> g[i][j];
}
}
bfs(n - 1, n - 1);
int x = 0, y = 0;
while (true) {
cout << x << ' ' << y << endl;
if (x == n - 1 && y == n - 1) break;
PII t = path[x][y];
x = t.first, y = t.second;
}
return 0;
}
双向广搜
字符串变换
已知有两个字串 A, B 及一组字串变换的规则(至多 6 个规则):
A1→B1
A2→B2
若在 10 步(包含 10 步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出 NO ANSWER!。
#include <iostream>
#include <unordered_map>
#include <queue>
using namespace std;
string a[10], b[10];
int n;
int extend(queue<string> &qa, queue<string> &qb, unordered_map<string, int> &da, unordered_map<string, int> &db, string a[], string b[]) {
for (int k = 0, sk = qa.size(); k < sk; ++k) {
string t = qa.front(); qa.pop();
for (int i = 0; i < t.size(); ++i) {
for (int j = 0; j < n; ++j) {
if (t.substr(i, a[j].size()) == a[j]) {
string state = t.substr(0, i) + b[j] + t.substr(i + a[j].size());
if (da.count(state)) continue;
if (db.count(state)) return da[t] + 1 + db[state];
da[state] = da[t] + 1;
qa.push(state);
}
}
}
}
return 11;
}
int bfs(string st, string ed) {
queue<string> qa, qb;
unordered_map<string, int> da, db;
qa.push(st), da[st] = 0;
qb.push(ed), db[ed] = 0;
while (qa.size() && qb.size()) {
int t;
if (qa.size() <= qb.size()) t = extend(qa, qb, da, db, a, b);
else t = extend(qb, qa, db, da, b, a);
if (t <= 10) return t;
}
return 11;
}
int main(void) {
string st, ed;
cin >> st >> ed;
while (cin >> a[n] >> b[n]) n++;
int t = bfs(st, ed);
if (t > 10) cout << "NO ANSWER!";
else cout << t;
return 0;
}
剪枝
数独加强版
数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。
请编写一个程序填写数独。
#include <iostream>
using namespace std;
const int N = 9, M = 1 << 9;;
int row[N], col[N], cell[N][N];
int ones[M], mp[M];
string str;
void init() {
for (int i = 0; i < N; ++i) row[i] = col[i] = (1 << N) - 1;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j)
cell[i][j] = (1 << N) - 1;
}
}
void draw(int x, int y, int t, bool is_set) {
if (is_set) str[x * N + y] = '1' + t;
else str[x * N + y] = '.';
int v = 1 << t;
if (!is_set) v = -v;
row[x] -= v;
col[y] -= v;
cell[x / 3][y / 3] -= v;
}
int get(int x, int y) {
return row[x] & col[y] & cell[x / 3][y / 3];
}
int lowbit(int x) {
return x & -x;
}
bool dfs(int cnt) {
if (!cnt) return true;
int minv = 10, x, y;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (str[i * N + j] == '.') {
int state = get(i, j);
if (ones[state] < minv) {
minv = ones[state];
x = i, y = j;
}
}
}
}
int state = get(x, y);
for (int i = state; i; i -= lowbit(i)) {
int t = mp[lowbit(i)];
draw(x, y, t, true);
if (dfs(cnt - 1)) return true;
draw(x, y, t, false);
}
return false;
}
int main(void) {
for (int i = 0; i < N; ++i) mp[1 << i] = i;
for (int i = 0; i < 1 << N; ++i) {
for (int j = 0; j < N; ++j)
ones[i] += i >> j & 1;
}
while (cin >> str, str[0] != 'e') {
init();
int cnt = 0;
for (int i = 0, k = 0; i < N; ++i) {
for (int j = 0; j < N; ++j, ++k) {
if (str[k] != '.') {
int t = str[k] - '1';
draw(i, j, t, true);
} else cnt++;
}
}
dfs(cnt);
cout << str << endl;
}
return 0;
}
木棒
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n, w[N];
int length, sum;
bool st[N];
bool dfs(int u, int cur, int start) { // 当前组,当前组长度,枚举位置
if (u * length == sum) return true;
if (cur == length) return dfs(u + 1, 0, 0);
for (int i = start; i < n; ++i) {
if (st[i] || cur + w[i] > length) continue;
st[i] = true;
if (dfs(u, cur + w[i], i + 1)) return true;
st[i] = false;
if (!cur || cur + w[i] == length) return false;
int j = i;
while (j < n && w[j] == w[i]) j++;
i = j - 1;
}
return false;
}
int main(void) {
while (cin >> n, n) {
memset(st, false, sizeof st);
sum = 0;
for (int i = 0; i < n; ++i) cin >> w[i], sum += w[i];
sort(w, w + n);
reverse(w, w + n);
length = 1;
while (1) {
if (sum % length == 0 && dfs(0, 0, 0)) {
cout << length << endl;
break;
}
length++;
}
}
return 0;
}
迭代加深
加成序列
满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为“加成序列”:
X[1]=1
X[m]=n
X[1]<X[2]<…<X[m−1]<X[m]
对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。
你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的“加成序列”。
如果有多个满足要求的答案,只需要找出任意一个可行解。
#include <iostream>
using namespace std;
const int N = 110;
int path[N];
int n;
bool dfs(int u, int k) {
if (u == k) return path[u - 1] == n;
bool st[N] = {0};
st[1] = true;
for (int i = u - 1; i >= 0; --i) {
for (int j = i; j >= 0; --j) {
int s = path[i] + path[j];
if (s > n || st[s] || s <= path[u - 1]) continue;
st[s] = true;
path[u] = s;
if (dfs(u + 1, k)) return true;
}
}
return false;
}
int main(void) {
path[0] = 1;
while (cin >> n, n) {
int k = 1;
while (!dfs(1, k)) k++;
for (int i = 0; i < k; ++i) cout << path[i] << ' ';
cout << endl;
}
return 0;
}
双向DFS
送礼物
达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]。
达达的力气很大,他一次可以搬动重量之和不超过 W 的任意多个物品。
达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
1≤N≤46,
1≤W,G[i]≤231−1
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 55;
int w[N];
int weight[1 << 25], cnt;
int n, m, ans, k;
void dfs1(int u, int s) {
if (u == k) {
weight[cnt++] = s;
return ;
}
dfs1(u + 1, s);
if ((LL) w[u] + s <= m) dfs1(u + 1, s + w[u]);
}
void dfs2(int u, int s) {
if (u == n) {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if ((LL) weight[mid] + s <= m) l = mid;
else r = mid - 1;
}
ans = max(ans, weight[r] + s);
return ;
}
dfs2(u + 1, s);
if ((LL) w[u] + s <= m) dfs2(u + 1, s + w[u]);
}
int main(void) {
cin >> m >> n;
for (int i = 0; i < n; ++i) cin >> w[i];
sort(w, w + n);
reverse(w , w + n);
k = n / 2 + 2;
dfs1(0, 0);
sort(weight, weight + cnt);
cnt = unique(weight, weight + cnt) - weight;
dfs2(k, 0);
cout << ans;
return 0;
}
IDA*
排书
给定 n 本书,编号为 1∼n。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照 1∼n 的顺序依次排列。
求最少需要多少次操作。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 22;
int n, q[N];
int w[5][N];
int f() {
int tot = 0;
for (int i = 0; i + 1 < n; ++i) {
if (q[i + 1] != q[i] + 1) tot++;
}
return (tot + 2) / 3;
}
bool dfs(int u, int depth) { // 当前深度,最大深度
if (u + f() > depth) return false;
if (f() == 0) return true;
for (int len = 1; len <= n; ++len) {
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
for (int k = j + 1; k < n; ++k) {
memcpy(w[u], q, sizeof q);
int x, y;
for (x = j + 1, y = i; x <= k; ++x, ++y) q[y] = w[u][x];
for (x = i; x <= j; ++x, ++y) q[y] = w[u][x];
if (dfs(u + 1, depth)) return true;
memcpy(q, w[u], sizeof q);
}
}
}
return false;
}
int main(void) {
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 0; i < n; ++i) cin >> q[i];
int depth = 0;
while (depth < 5 && !dfs(0, depth)) depth++;
if (depth >= 5) puts("5 or more");
else cout << depth << endl;
}
return 0;
}