搜索算法的一些模板

搜索

最短路模型

迷宫问题

其中的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;
}
posted @ 2021-07-10 14:19  yangruomao  阅读(51)  评论(0编辑  收藏  举报