AcWing-算法提高课(第二章)

Flood Fill

池塘计数

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;
const int M = 1e3 + 10;

int n, m;
char g[N][M];
PII q[N * M];
bool st[N][M];

void bfs(int sx, int sy)
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[hh] = {sx, sy};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = t.x - 1; i <= t.x + 1; i++)
            for (int j = t.y - 1; j <= t.y + 1; j++)
            {
                if (i == sx && j == sy)
                    continue;
                if (i < 0 || i >= n || j < 0 || j >= m)
                    continue;
                if (g[i][j] == '.' || st[i][j])
                    continue;
                    
                st[i][j] = true;// 已经遍历过的,就标记一下
                
                q[++tt] = {i, j};
            }
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        scanf("%s", g[i]);
        
    int cnt = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'W' && !st[i][j])
            {
                bfs(i, j);
                cnt++;
            }
            
    // 输出结果
    printf("%d\n", cnt);
    
    return 0;
}

城堡问题

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 50 + 10;
const int M = 50 + 10;

int n, m;
int g[N][M];
PII q[N * M];
bool st[N][M];

int bfs(int sx, int sy)
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int area = 0;
    
    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        area++;
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (st[a][b])
                continue;
            if (g[t.x][t.y] >> i & 1)// 需要判断一下该方向是否有墙壁堵着
                continue;
                
            st[a][b] = true;// 已经遍历过的,就标记一下
            
            q[++tt] = {a, b};
        }
    }
    
    return area;
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            scanf("%d", &g[i][j]);
            
    int cnt = 0;
    int area = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (!st[i][j])
            {
                area = max(area, bfs(i, j));
                cnt++;
            }
    
    // 输出结果
    printf("%d\n%d\n", cnt, area);
    
    return 0;
}

山峰和山谷

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;

int n;
int h[N][N];
PII q[N * N];
bool st[N][N];

void bfs(int sx, int sy, bool &has_higher, bool &has_lower)// 这里使用了引用类型的入参
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = t.x - 1; i <= t.x + 1; i++)
            for (int j = t.y - 1; j <= t.y + 1; j++)
            {
                if (i == t.x && j == t.y)
                    continue;
                if (i < 0 || i >= n || j < 0 || j >= n)
                    continue;
                    
                if (h[i][j] != h[t.x][t.y])
                {
                    if (h[i][j] > h[t.x][t.y])
                        has_higher = true;
                    else
                        has_lower = true;
                }
                else if (!st[i][j])
                {
                    st[i][j] = true;// 已经遍历过的,就标记一下
                    
                    q[++tt] = {i, j};
                }
            }
    }
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d\n", &h[i][j]);
            
    int peak = 0;
    int valley = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (!st[i][j])
            {
                bool has_higher = false, has_lower = false;
                
                bfs(i, j, has_higher, has_lower);
                
                if (!has_higher)
                    peak++;
                if (!has_lower)
                    valley++;
            }
            
    // 输出结果
    printf("%d %d\n", peak, valley);
    
    return 0;
}

最短路模型

迷宫问题

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;

int n;
int g[N][N];
PII q[N * N];
PII pre[N][N];

void bfs(int sx, int sy)
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
    
    memset(pre, -1, sizeof pre);
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= n)
                continue;
            if (g[a][b])// 判断是否为墙壁
                continue;
            if (pre[a][b].x != -1)// 判断是否已经被搜到过
                continue;
                
            pre[a][b] = t;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &g[i][j]);
            
    bfs(n - 1, n - 1);// 从迷宫的右下角开始进行bfs操作
    
    PII end = {0, 0};
    while (true)
    {
        printf("%d %d\n", end.x, end.y);
        if (end.x == n - 1 && end.y == n - 1)
            break;
        end = pre[end.x][end.y];
    }
    
    return 0;
}

武士风度的牛

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 150 + 10;
const int M = 150 + 10;

int n, m;
char g[N][M];
PII q[N * M];
int dist[N][M];

int bfs()
{
    // 先找到起点的坐标
    int sx, sy;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'K')
                sx = i, sy = j;
                
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
    int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
    
    memset(dist, -1, sizeof dist);
    dist[sx][sy] = 0;
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 8; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (g[a][b] == '*')
                continue;
            if (g[a][b] == 'H')// 题目的数据保证一定有解
                return dist[t.x][t.y] + 1;
            if (dist[a][b] != -1)// 判断是否已经被搜到过
                continue;
                
            dist[a][b] = dist[t.x][t.y] + 1;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d %d", &m, &n);
    
    for (int i = 0; i < n; i++)
        scanf("%s", &g[i]);
        
    printf("%d\n", bfs());
    
    return 0;
}

抓住那头牛

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n, k;
int q[N];
int dist[N];

int bfs()
{
    int hh = 0, tt = 0;
    q[0] = n;
    
    memset(dist, -1, sizeof dist);
    dist[n] = 0;
    
    while (hh <= tt)
    {
        int t = q[hh++];
        
        if (t == k)
            return dist[t];
            
        if (t - 1 >= 0 && dist[t - 1] == -1)
        {
            dist[t - 1] = dist[t] + 1;
            q[++tt] = t - 1;
        }
        if (t + 1 <= k && dist[t + 1] == -1)
        {
            dist[t + 1] = dist[t] + 1;
            q[++tt] = t + 1;
        }
        //  只要t*2大于k+1,我们就可以先减再乘
        if (t * 2 <= k + 1 && dist[t * 2] == -1)
        {
            dist[t * 2] = dist[t] + 1;
            q[++tt] = t * 2;
        }
    }
}

int main()
{
    scanf("%d %d", &n, &k);
    
    printf("%d\n", bfs());
    
    return 0;
}

多源BFS

矩阵距离

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;
const int M = 1e3 + 10;

int n, m;
char g[N][M];// 因为数字之间没有空格,所以用char类型读入
PII q[N * M];
int dist[N][M];

void bfs()
{
    memset(dist, -1, sizeof dist);
    
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == '1')
            {
                dist[i][j] = 0;
                q[++tt] = {i, j};
            }
            
    int dx[4] = {0, 1, 0, -1};
    int dy[4] = {1, 0, -1, 0};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (dist[a][b] != -1)// 判断是否已经被搜到过
                continue;
                
            dist[a][b] = dist[t.x][t.y] + 1;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        // 因为数字之间没有空格,所以用char类型读入
        scanf("%s", g[i]);
        
    bfs();
    
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
            printf("%d ", dist[i][j]);
        puts("");
    }
    
    return 0;
}

最小步数模型

魔板

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>

using namespace std;

char g[2][4];
unordered_map<string, int> dist;
unordered_map<string, pair<char, string>> pre;

void set(string state)// 由当前字符串得到对应的魔板
{
    for (int j = 0; j < 4; j++)
        g[0][j] = state[j];
    for (int j = 0, i = 7; j < 4; j++, i--)
        g[1][j] = state[i];
}

string get()// 由当前魔板得到对应的字符串
{
    string res = "";
    for (int j = 0; j < 4; j++)
        res += g[0][j];
    for (int j = 3; j >= 0; j--)
        res += g[1][j];
    return res;
}

string move0(string state)// 执行操作A,交换上下两行
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    for (int j = 0; j < 4; j++)
        swap(g[0][j], g[1][j]);
        
    // 由当前魔板得到对应的字符串
    return get();
}

string move1(string state)// 执行操作B,将最右边的一列插入到最左边
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    char t1 = g[0][3], t2 = g[1][3];
    for (int i = 0; i < 2; i++)
        for (int j = 3; j >= 1; j--)
            g[i][j] = g[i][j - 1];
    g[0][0] = t1, g[1][0] = t2;
    
    // 由当前魔板得到对应的字符串
    return get();
}

string move2(string state)// 执行操作C,魔板中央对的4个数作顺时针旋转
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    char t = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = t;
    
    // 由当前魔板得到对应的字符串
    return get();
}

int bfs(string start, string end)
{
    if (start == end)
        return 0;
        
    queue<string> q;
    q.push(start);
    
    dist[start] = 0;
    
    while (!q.empty())
    {
        string t = q.front();
        q.pop();
        
        string m[3];
        m[0] = move0(t);// 执行操作A,交换上下两行
        m[1] = move1(t);// 执行操作B,将最右边的一列插入到最左边
        m[2] = move2(t);// 执行操作C,魔板中央对的4个数作顺时针旋转
        
        for (int i = 0; i < 3; i++)
            if (!dist.count(m[i]))
            {
                dist[m[i]] = dist[t] + 1;
                
                pre[m[i]] = {char(i + 'A'), t};
                
                if (m[i] == end)
                    return dist[m[i]];
                    
                q.push(m[i]);
            }
    }
}

int main()
{
    string start = "12345678";
    string end = "";
    
    for (int i = 0; i < 8; i++)
    {
        int x;
        scanf("%d", &x);
        end += char(x + '0');
    }
    
    int t = bfs(start, end);
    
    printf("%d\n", t);
    
    if (t > 0)// 如果操作序列的长度大于0,则在第二行输出字典序最小的操作序列
    {
        string op = "";
        
        while (end != start)// 从特殊状态往前追溯至基本状态
        {
            op += pre[end].first;
            end = pre[end].second;
        }
        
        // 记得要反转一下,这样才是从基本状态到特殊状态的操作序列
        reverse(op.begin(), op.end());
        
        printf("%s\n", op.c_str());// 输出字典序最小的操作序列
    }
    
    return 0;
}

双端队列广搜

电路维修

#include <iostream>
#include <cstring>
#include <deque>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 500 + 10;
const int M = 500 + 10;

int n, m;
char g[N][M];
int dist[N][M];
bool st[N][M];

int bfs()// 这里的bfs思路,本质上就是Dijkstra思路
{
    memset(st, false, sizeof st);
    
    memset(dist, 0x3f, sizeof dist);
    dist[0][0] = 0;
    
    deque<PII> q;// 双端队列
    q.push_front({0, 0});
    
    int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
    int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};
    
    // 根据四个方向偏移,所能走到的四种电路的形状,分别为:左上\,右上/,右下\,左下/
    char cs[4] = {'\\', '/', '\\', '/'};
    
    while (!q.empty())
    {
        PII t = q.front();
        q.pop_front();
        
        if (t.x == n && t.y == m)
            return dist[t.x][t.y];
            
        if (st[t.x][t.y])
            continue;
            
        st[t.x][t.y] = true;
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a > n || b < 0 || b > m)
                continue;
                
            int ia = t.x + ix[i], ib = t.y + iy[i];
            
            int d = dist[t.x][t.y] + (g[ia][ib] != cs[i]);
            
            if (d < dist[a][b] && dist[a][b] == 0x3f3f3f3f)
            {
                dist[a][b] = d;
                
                if (g[ia][ib] != cs[i])
                    q.push_back({a, b});
                else
                    q.push_front({a, b});
            }
            if (d < dist[a][b] && dist[a][b] != 0x3f3f3f3f)
            {
                dist[a][b] = d;
                
                q.push_front({a, b});
            }
        }
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d %d", &n, &m);
        
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);

        if ((n + m) & 1)// 通过观察可以知道,当终点的横纵坐标的和为奇数时,则起点永远无法连通终点
            printf("NO SOLUTION\n");
        else
            printf("%d\n", bfs());
    }
    
    return 0;
}

双向广搜

字串变换

#include <iostream>
#include <queue>
#include <unordered_map>

using namespace std;

const int N = 6;

string a, b;
int n;
string oa[N], ob[N];

int extend(
    queue<string>& qa,
    unordered_map<string, int>& da,
    unordered_map<string, int>& db,
    string oa[],
    string ob[]
)
{
    int d = da[qa.front()];
    
    while (qa.size() && da[qa.front()] == d)// 扩展完整一层
    {
        string t = qa.front();
        qa.pop();
        
        for (int i = 0; i < n; i++)// 枚举规则
            for (int j = 0; j < t.size(); j++)// 枚举当前字符串的每个位置
                // 判断当前字符串的当前位置是否符合当前规则进行变换
                if (t.substr(j, oa[i].size()) == oa[i])
                {
                    // 变换后的字符串
                    string u = t.substr(0, j) + ob[i] + t.substr(j + oa[i].size());
                    
                    if (da.count(u))// 判断从出发的这一边是否已经搜索过
                        continue;
                        
                    if (db.count(u))// 如果在另一边已经搜索过,则表示从出发的这一边可以变换到另一边
                        return da[t] + db[u] + 1;
                        
                    qa.push(u);
                    
                    da[u] = da[t] + 1;
                }
    }
    
    // 当扩展完整一层后,还没有找到结果的话,则继续操作下一层
    return 11;
}

int bfs()// 双向bfs
{
    if (a == b)
        return 0;
        
    queue<string> qa, qb;
    qa.push(a), qb.push(b);
    
    unordered_map<string, int> da, db;
    da[a] = 0, db[b] = 0;
    
    int step = 0;// 记录变换步数
    
    while (qa.size() && qb.size())
    {
        int t;
        
        // 每次扩展时,选择队列中数量较少的一边来进行扩展,每次每边扩展完整一层
        if (qa.size() <= qb.size())
            t = extend(qa, da, db, oa, ob);
        else
            t = extend(qb, db, da, ob, oa);
        
        if (t <= 10)
            return t;
            
        step++;
        
        // 当变换步数大于等于10步时,还没有找到结果的话,则可以结束循环了
        if (step >= 10)
            break;
    }
    
    return -1;
}

int main()
{
    cin >> a >> b;
    
    while (cin >> oa[n] >> ob[n])// 输入规则
        n++;
        
    int t = bfs();// 双向bfs
    
    if (t == -1)
        printf("NO ANSWER!\n");
    else
        printf("%d\n", t);
        
    return 0;
}

A*

八数码

#include <iostream>
#include <unordered_map>
#include <queue>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<string, char> PSC;
typedef pair<int, string> PIS;

// 预估代价函数
int f(string state)
{
    int cnt = 0;
    
    for (int i = 0; i < state.size(); i++)
        if (state[i] != 'x')
        {
            int t = state[i] - '1';
            cnt += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
        }
    
    // 返回估计距离,值为当前状态中的,每个数的位置与它对应的目标位置的曼哈顿距离,的总和
    return cnt;
}

string bfs(string start)
{
    string end = "12345678x";
    
    unordered_map<string, int> dist;
    unordered_map<string, PSC> prev;
    priority_queue<PIS, vector<PIS>, greater<PIS>> q;// 优先队列小根堆
    
    dist[start] = 0;
    /*
        优先队列小根堆中每个状态的权重是:
            当前代价 + 预估代价
            (其中,
            当前代价为从起点到该状态的真实距离(注意并不一定是最短距离),
            预估代价为从该状态到终点的估计距离,估计距离一般通过预估代价函数得到)
    */
    q.push({0 + f(start), start});
    
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    char op[4] = {'u', 'r', 'd', 'l'};
    
    while (!q.empty())
    {
        PIS t = q.top();
        q.pop();
        
        string state = t.y;
        
        // 当终点首次弹出时,则一定是最优解,然后退出循环即可
        if (state == end)
            break;
        
        int x, y;
        for (int i = 0; i < state.size(); i++)
            if (state[i] == 'x')
            {
                x = i / 3;
                y = i % 3;
            }
            
        string source = state;
        
        for (int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            
            if (a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                // 把当前状态的网格转换成新状态的网格
                swap(state[x * 3 + y], state[a * 3 + b]);
                
                // 注意,在A*算法中,当第一次搜到某个点的时候,距离不一定是最短的,其dist[]的值是不断变小的,所以要加后一个判断
                if (!dist.count(state) || dist[state] > dist[source] + 1)
                {
                    // 更新当前状态的当前代价
                    dist[state] = dist[source] + 1;
                    
                    // 更新优先队列小根堆
                    q.push({dist[state] + f(state), state});
                    
                    // 记录当前状态是从哪个状态走过来的,且对应哪个操作
                    prev[state] = {source, op[i]};
                }
                
                // 状态恢复,继续遍历当前这个点(x,y)可以往哪个方向走
                swap(state[x * 3 + y], state[a * 3 + b]);
            }
        }
    }
    
    // 得到操作方案
    string res;
    while (end != start)
    {
        res += prev[end].y;
        end = prev[end].x;
    }
    reverse(res.begin(), res.end());// 记得要反转一下
    
    return res;
}

int main()
{
    string g, c, seq;
    
    while (cin >> c)
    {
        g += c;
        if (c != "x")
            seq += c;
    }
    
    // 计算逆序对的数量
    int t = 0;
    for (int i = 0; i < seq.size(); i++)
        for (int j = i + 1; j < seq.size(); j++)
            if (seq[i] > seq[j])
                t++;
    
    if (t % 2)
        // 在八数码问题中,逆序对的数量为奇数时,则八数码问题没有解
        printf("unsolvable\n");
    else
        printf("%s\n", bfs(g).c_str());
        
    return 0;
}

第K短路

#include <iostream>
#include <cstring>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
typedef pair<int, PII> PIPII;

const int N = 1e3 + 10;
const int M = 2 * 1e4 + 10;// 因为要反向建图,所以用到的边数需要乘2

int n, m;
int h[N], rh[N], w[M], e[M], ne[M], idx;
int s, t, k;
int dist[N];
int cnt[N];

void add(int temp[], int a, int b, int c)
{
    w[idx] = c;
    e[idx] = b;
    ne[idx] = temp[a];
    temp[a] = idx++;
}

// 预估代价函数
void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[t] = 0;
    
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, t});
    
    while (!q.empty())
    {
        PII temp = q.top();
        q.pop();
        
        int ver = temp.y;
        int distance = temp.x;
        
        for (int i = rh[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                q.push({dist[j], j});
            }
        }
    }
}

int astar()
{
    priority_queue<PIPII, vector<PIPII>, greater<PIPII>> q;// 优先队列小根堆
    /*
        优先队列小根堆中每个状态的权重是:
            当前代价 + 预估代价
            (其中,
            当前代价为从起点到该状态的真实距离(注意并不一定是最短距离),
            预估代价为从该状态到终点的估计距离,估计距离一般通过预估代价函数得到)
    */
    q.push({0 + dist[s], {0, s}});
    
    while (!q.empty())
    {
        PIPII temp = q.top();
        q.pop();
        
        int ver = temp.y.y;
        
        cnt[ver]++;// 计算当前点第几次弹出优先队列小根堆
        
        if (ver == t && cnt[ver] == k)
            return temp.y.x;
            
        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            
            /*
                可以对最短路径的搜索进行优化,
                每个点第几次弹出优先队列小根堆就是第几短路径,
                当某个点第k次弹出优先队列小根堆,准备要进行第k+1次进入优先队列小根堆时,
                说明该点到终点的路径一定不会是前k短路径。
                
                这个优化,可以把起点和终点不连通的情况也考虑到,防止TLE(Time Limit Exceeded)
            */
            if (cnt[j] < k)
                q.push({temp.y.x + w[i] + dist[j], {temp.y.x + w[i], j}});
        }
    }
    
    return -1;
}

int main()
{
    scanf("%d %d", &n, &m);
    
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        
        add(h, a, b, c);
        add(rh, b, a, c);// 反向建图的目的是为了反向做一次dijkstra操作,充当了预估代价函数的作用
    }
    
    scanf("%d %d %d", &s, &t, &k);
    
    /*
        当起点和终点相同时,
        k中隐含了一条长度为0的没有边的最短路,
        但这条最短路是不对的,
        因为题目要求起点和终点之间至少包含一条边,
        所以k++,是为了排除掉长度为0的最短路
    */
    if (s == t)
        k++;
        
    dijkstra();
    
    printf("%d\n", astar());
    
    return 0;
}

知识点补充

  • 在Dijkstra算法中,当某个点若干次被搜索到的时候,该点的最短路还没有被最终确定,只有当该点从优先队列小根堆中第一次被弹出时,该点的距离才是被最终确定的最短路的距离
  • 在BFS算法中,当某个点第一次被搜索到的时候,该点的最短路就已经被最终确定。BFS算法本质上是Dijkstra算法的特殊情况
  • 在A*算法中,当某个点若干次被搜索到的时候,该点的最短路还没有被最终确定,只有当该点从优先队列小根堆中第一次被弹出时,该点的距离才是被最终确定的最短路的距离(其中,闫学灿的观点认为,只有该点为终点时才成立。与《算法竞赛进阶指南》书中的观点不一致)。A*算法的思路就是Dijkstra算法的思路,区别在于优先队列小根堆中的权重是,当前代价与预估代价,的总和

DFS之连通性模型

迷宫

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100 + 10;

int n;
char g[N][N];
int xa, ya, xb, yb;
bool st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool dfs(int x, int y)
{
    if (g[x][y] == '#')
        return false;
            
    // 判断是否找到终点
    if (x == xb && y == yb)
        return true;
        
    st[x][y] = true;
    
    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)
            continue;
        if (st[a][b])
            continue;
            
        if (dfs(a, b))
            return true;
    }
    
    return false;
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d", &n);
        
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);
            
        scanf("%d %d %d %d", &xa, &ya, &xb, &yb);
        
        memset(st, false, sizeof st);
        
        printf("%s\n", dfs(xa, ya) ? "YES" : "NO");
    }
    
    return 0;
}

红与黑

#include <iostream>
#include <cstring>

using namespace std;

const int N = 20 + 10;
const int M = 20 + 10;

int n, m;
char g[N][M];
bool st[N][M];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)
{
    int cnt = 1;
    
    st[x][y] = true;
    
    for (int i = 0; i < 4; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        
        if (a < 0 || a >= n || b < 0 || b >= m)
            continue;
        if (g[a][b] == '#')
            continue;
        if (st[a][b])
            continue;
            
        cnt += dfs(a, b);
    }
    
    return cnt;
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);
            
        int a, b;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (g[i][j] == '@')
                    a = i, b = j;
                    
        memset(st, false, sizeof st);
        
        printf("%d\n", dfs(a, b));
    }
    
    return 0;
}

DFS之搜索顺序

马走日

#include <iostream>

using namespace std;

const int N = 10;
const int M = 10;

int n, m;
int x, y;
int ans;
bool st[N][M];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

void dfs(int x, int y, int cnt)
{
    if (cnt == n * m)
    {
        ans++;
        return ;
    }
    
    st[x][y] = true;
    
    for (int i = 0; i < 8; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        
        if (a < 0 || a >= n || b < 0 || b >= m)
            continue;
        if (st[a][b])
            continue;
            
        dfs(a, b, cnt + 1);
    }
    
    st[x][y] = false;// 恢复现场
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d %d %d %d", &n, &m, &x, &y);
        
        ans = 0;
        
        dfs(x, y, 1);
        
        printf("%d\n", ans);
    }
    
    return 0;
}

单词接龙

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 20;

int n;
string word[N];
int g[N][N];
int ans;
int used[N];

void dfs(string dragon, int before)
{
    /*
        在C++中,string类型的size()函数的返回值是一个无符号类型的值
        在C++中,max()函数的两个入参的类型必须要保持一致
    */
    ans = max(ans, (int)dragon.size());
    
    used[before]++;
    
    for (int j = 0; j < n; j++)
        if (g[before][j] && used[j] < 2)
            dfs(dragon + word[j].substr(g[before][j]), j);

    used[before]--;// 恢复现场
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        cin >> word[i];
        
    char start;
    cin >> start;
    
    // 预处理
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            string a = word[i];
            string b = word[j];
            
            // 我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度
            for (int k = 1; k < min(a.size(), b.size()); k++)
                if (a.substr(a.size() - k, k) == b.substr(0, k))
                {
                    g[i][j] = k;
                    break;
                }
        }
        
    for (int i = 0; i < n; i++)
        if (word[i][0] == start)
            dfs(word[i], i);
            
    printf("%d\n", ans);
    
    return 0;
}

分成互质组

#include <iostream>

using namespace std;

const int N = 10;

int n;
int a[N];
int ans = N;// 最坏情况下是,每个数单独一个组
bool st[N];
int group[N][N];

// 欧几里得算法求两个数的最大公约数
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

bool check(int group[], int gc, int b)
{
    for (int i = 0; i < gc; i++)
        // 如果两个数的最大公约数大于1,则两个数不互质
        if (gcd(a[group[i]], b) > 1)
            return false;
            
    return true;
}

/*
    四个参数的含义分别为:
        当前搜索到第几组、
        当前组中一共有几个数、
        一共搜索过几个数、
        从第几个数(具体数值为a数组的下标)开始进行搜索
*/
void dfs(int g, int gc, int tc, int start)// 这个start可以减掉很多组内的等效冗余的方案
{
    if (g >= ans)
        return ;
        
    if (tc == n)
        ans = g;
        
    bool flag = true;
    
    for (int i = start; i < n; i++)
        // 判断当前a[i]这个数是否被用过,并且,当前a[i]这个数,是否和当前组g中的一共gc个数都互质
        if (!st[i] && check(group[g], gc, a[i]))
        {
            st[i] = true;
            
            // 把当前a[i]这个数加入到当前组g中,然后继续进行dfs操作。这个操作可以不恢复现场
            group[g][gc] = i;
            
            dfs(g, gc + 1, tc + 1, i + 1);
            
            st[i] = false;// 恢复现场
            
            // 如果至少有一个数可以放入到当前组中,则不需要新开一个新的组来进行dfs操作
            flag = false;
            
            /*
                剪枝操作,
                因为开一个新组的前提条件是每个数都不能放到旧组中了,
                所以剩下的第一个数一定会放到某个新组中,那我们可以将它固定到第一个新组中,
                这样可以减掉很多组与组之间的等效冗余的方案
            */
            if (gc == 0)
                return ;
        }
    
    // 如果至少有一个数可以放入到当前组中,则不需要新开一个新的组来进行dfs操作
    if (flag)
        dfs(g + 1, 0, tc, 0);
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    
    // 这个dfs的搜索顺序本质上是,按照每个组的每个位置进行搜索的!!!
    dfs(1, 0, 0, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

DFS之剪枝与优化

小猫爬山

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 18;

int n, m;
int w[N];
int ans = N;// 最坏情况下是,每只猫单独一辆缆车
int sum[N];

void dfs(int u, int k)
{
    if (k >= ans)// 最优性剪枝
        return ;
        
    if (u == n)
        ans = k;
        
    for (int i = 0; i < k; i++)
        if (sum[i] + w[u] <= m)// 可行性剪枝
        {
            sum[i] += w[u];
            dfs(u + 1, k);
            sum[i] -= w[u];// 恢复现场
        }
    
    /*
        这里并不能使用贪心,举出例子:
        请尝试在maxV=16时,重量为9 5 5 5 4 3
        贪心结果是9+5 5+5+4 3,结果为3
        但正确结果为9+4+3 5+5+5,结果为2
    */
    
    // 新开一辆缆车
    sum[k] = w[u];
    dfs(u + 1, k + 1);
    sum[k] = w[u];// 恢复现场,这个现场不恢复也不影响最终答案
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &w[i]);
    
    // 优化搜索顺序
    sort(w, w + n);
    reverse(w, w + n);
    
    // 这个dfs的搜索顺序本质上是,按照每个小猫能放到哪个缆车上进行搜索的!!!
    dfs(0, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

数独

#include <iostream>

using namespace std;

const int N = 9;

char str[100];
int row[N], col[N], cell[3][3];
int ones[1 << N];
int map[1 << N];

// 初始化处理,先默认每一行、每一列、每一个3×3的九宫格都可以不重复地填数字1~9
void init()
{
    for (int i = 0; i < N; i++)
        row[i] = col[i] = (1 << N) - 1;
    
    // 一共有9个3x3的九宫格
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            cell[i][j] = (1 << N) - 1;
}

// 处理第x行、第y列、(x,y)所属的3×3的九宫格,可行性剪枝
void draw(int x, int y, int t, int flag)
{
    if (flag)
        str[x * N + y] = '1' + t;
    else
        str[x * N + y] = '.';
        
    int v = 1 << t;
    
    if (!flag)
        v = 0 - 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 + 1);
}

bool dfs(int cnt)
{
    if (!cnt)
        return true;
        
    // 优化搜索顺序,让dfs的搜索顺序从,可以选择填写的数字个数较少的位置,开始进行搜索
    int minv = 10;
    int x, y;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (str[i * N + j] == '.')
            {
                int state = get(i, j);
                
                if (ones[state] < minv)
                {
                    minv = ones[state];
                    x = i, y = j;
                }
            }
            
    // 优化搜索顺序,让dfs的搜索顺序从,可以选择填写的数字的个数较少的位置,开始进行搜索
    int state = get(x, y);
    for (int i = state; i; i -= lowbit(i))
    {
        int t = map[lowbit(i)];
        
        draw(x, y, t, true);
        
        if (dfs(cnt - 1))
            return true;
            
        draw(x, y, t, false);// 恢复现场
    }
    
    // 当state等于0时,说明已经出现了,存在有一个位置无法填写数字,的情况,即可返回false
    return false;
}

int main()
{
    // 位运算优化
    for (int i = 0; i < (1 << N); i++)
        for (int j = 0; j < N; j++)
            ones[i] += (i >> j) & 1;
    
    for (int i = 0; i < N; i++)
        map[1 << i] = i;
        
    while (cin >> str, str[0] != 'e')
    {
        init();// 初始化处理,先默认每一行、每一列、每一个3×3的九宫格都可以不重复地填数字1~9
        
        int cnt = 0;
        
        for (int i = 0; i < N; i++)
            for (int j = 0; j < N; j++)
                if (str[i * N + j] != '.')
                {
                    int t = str[i * N + j] - '1';
                    
                    // 处理第i行、第j列、(i,j)所属的3×3的九宫格,可行性剪枝
                    draw(i, j, t, true);
                }
                else
                    cnt++;
                    
        if (dfs(cnt))// 题目保证输入中的每个谜题都只有一个解决方案
            cout << str << endl;
    }
    
    return 0;
}

木棒

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 70;

int n;
int w[N];
int sum;
int length;
bool st[N];

/*
    三个参数的含义分别为:
        一共搜索过几个木棒、
        当前木棒的长度、
        从第几个木棍(具体数值为w数组的下标)开始进行搜索
*/
bool dfs(int u, int ul, int start)// 这个start可以减掉很多组内的等效冗余的方案,这是一个优化
{
    if (u * length == sum)
        return true;
        
    if (ul == length)
        return dfs(u + 1, 0, 0);
        
    for (int i = start; i < n; i++)
    {
        // 可行性剪枝
        if (st[i] || ul + w[i] > length)
            continue;
            
        st[i] = true;
        
        if (dfs(u, ul + w[i], i + 1))
            return true;
            
        st[i] = false;// 恢复现场
        
        // 如果当前木棒放第一根木棍导致后续方案失败,则整个方案一定失败,这是一个优化
        if (!ul)
            return false;
            
        // 如果当前木棒放最后一根木棍可以让当前这根木棒凑成length,但是导致后续方案失败,则整个方案一定失败,这是一个优化
        if (ul + w[i] == length)
            return false;
            
        // 如果当前木棍放到当前木棒中失败了,则直接略过后面所有长度相等的木棍,这是一个优化
        int j = i;
        while (j < n && w[j] == w[i])
            j++;
        i = j - 1;
    }
    
    return false;
}

int main()
{
    while (cin >> n, n)
    {
        memset(st, false, sizeof st);
        
        sum = 0;
        
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &w[i]);
            sum += w[i];
        }
        
        // 优化搜索顺序
        sort(w, w + n);
        reverse(w, w + n);
        
        length = w[0];// 木棒的可能最小长度,可以从最长的木棍开始进行枚举,这是一个优化
        
        while (true)
        {
            // 因为木棒是等长的,因此必须要满足sum % length == 0,这是一个优化
            if (sum % length == 0 && dfs(0, 0, 0))
            {
                printf("%d\n", length);
                break;
            }
            
            length++;
        }
    }
    
    return 0;
}

生日蛋糕

#include <iostream>
#include <cmath>

using namespace std;

const int M = 25;

int n, m;
int r[M], h[M];
int ans;
int minv[M], mins[M];

void dfs(int u, int v, int s)
{
    /*
        当已经消耗了的体积v加上前u层最小的体积都大于给定的体积n时,
        表示不合法,
        即可结束dfs操作,
        可行性剪枝
    */
    if (v + minv[u] > n)
        return ;
    /*
        当已经消耗了的表面积s加上前u层最小的侧表面积都大于等于结果ans时,
        表示结果已经无法再进行优化,
        即可结束dfs操作,
        最优性剪枝
    */
    if (s + mins[u] >= ans)
        return ;
    
    /*
        设当前搜索到第u层,已经消耗了体积v,已经消耗了表面积s,
        即有n - v = r[1] * r[1] * h[1] + r[2] * r[2] * h[2] + ...... + r[u] * r[u] * h[u],
        然后设前u层的侧表面积为S[u],
        即有S[u] = 2 * r[1] * h[1] + 2 * r[2] * h[2] + ...... + 2 * r[u] * h[u]
                 = 2 * (r[1] * h[1] + r[2] * h[2] + ...... + r[u] * h[u])
                 = 2 / r[u + 1] * (r[1] * h[1] + r[2] * h[2] + ...... + r[u] * h[u]) * r[u + 1]
                 = 2 / r[u + 1] * (r[1] * r[u + 1] * h[1] + r[2] * r[u + 1] * h[2] + ...... + r[u] * r[u + 1] * h[u]),
        可得:
            S[u] >= 2 / r[u + 1] * (r[1] * r[1] * h[1] + r[2] * r[2] * h[2] + ...... + r[u] * r[u] * h[u])
        即有S[u] >= 2 / r[u + 1] * (n - v),
        即我们发现了前u层的侧表面积的下界为2 / r[u + 1] * (n - v),
        当已经消耗了的表面积s加上前u层的侧表面积的下界都大于等于结果ans时,
        表示结果已经无法再进行优化,
        即可结束dfs操作,
        最优性剪枝
    */
    if (s + 2 * (n - v) / r[u + 1] >= ans)// 最优性剪枝
        return ;
    
    if (!u)
    {
        if (v == n)
            ans = s;
            
        return ;
    }
    
    // 先搜半径r,再搜高度h,因为半径r会涉及到平方级别(圆柱体的体积为:r * r * h),从大到小搜,优化搜索顺序
    /*
        设当前搜索到第u层,已经消耗了体积v,
        因为每一层的高度和半径至少为1且每一层的高度和半径自底向上是递减的,
        所以就有:
            u <= r[u] <= r[u + 1] - 1
            u <= h[u] <= h[u + 1] - 1
        同时还要保证体积不超过n,
        即有r[u] * r[u] * h[u] <= n - v,可得:
            r[u] <= sqrt((n - v) / h[u]),其中h[u]的最小值为u,即有r[u] <= sqrt((n - v) / u)
            h[u] <= (n - v) / r[u] / r[u]
        然后两个上界取最小,
        可行性剪枝
    */
    for (int i = min(r[u + 1] - 1, (int)sqrt((n - v) / u)); i >= u; i--)// 在C++中,min()函数的两个入参的类型必须要保持一致
        for (int j = min(h[u + 1] - 1, (n - v) / i / i); j >= u; j--)
        {
            int t = 0;
            if (u == m)
                // 只有当搜到第m层时,才会把俯视视角所看到的蛋糕的表面积加上
                t = i * i;
            
            r[u] = i, h[u] = j;
            
            dfs(u - 1, v + i * i * j, s + 2 * i * j + t);
        }
}

// 注意!!!根据题意可知,我们在计算中不涉及到π,相当于可以把π忽略
int main()
{
    scanf("%d %d", &n, &m);
    
    // 预处理,假设第一层半径和高度为1,第二层半径和高度为2,第三层半径和高度为3,......,第m层半径和高度为m
    for (int i = 1; i <= m; i++)
    {
        minv[i] = minv[i - 1] + i * i * i;// 前u层最小的体积
        mins[i] = mins[i - 1] + 2 * i * i;// 前u层最小的侧表面积
    }
    
    r[m + 1] = h[m + 1] = 0x3f3f3f3f;
    ans = 0x3f3f3f3f;
    
    /*
        我们这里设最上面一层为第1层,即半径和高度最小的一层,
        最下面一层为第m层,即半径和高度最大的一层,
        自底向上搜,优化搜索顺序
    */
    dfs(m, 0, 0);
    
    if (ans != 0x3f3f3f3f)
        printf("%d\n", ans);
    else
        printf("0\n");
    
    return 0;
}

迭代加深

加成序列

#include <iostream>

using namespace std;

const int N = 100 + 10;

int n;
int path[N];

bool dfs(int u, int depth)
{
    if (u == depth)
        return path[u - 1] == n;
    
    bool st[N] = {false};// 将st数组的值全部初始化成false
    
    // 从大到小枚举,优化搜索顺序
    for (int i = u - 1; i >= 0; i--)
        for (int j = i; j >= 0; j--)
        {
            int s = path[i] + path[j];
            
            // 可行性剪枝
            if (s > n)
                continue;
            
            // 可行性剪枝
            if (s <= path[u - 1])
                continue;
            
            // 这个st数组可以减掉很多组内的等效冗余的方案
            if (st[s])
                continue;
                
            st[s] = true;
            
            path[u] = s;
            
            if (dfs(u + 1, depth))
                return true;
        }
        
    return false;
}

int main()
{
    path[0] = 1;// 根据题意可知
    
    while (cin >> n, n)
    {
        int k = 1;
        /*
            这个上限每次往上加1,
            直至在当前上限进行dfs时能找到最优解,
            这种dfs求最小值的方法被称为迭代加深求最小值
        */
        while (!dfs(1, k))
            k++;
        
        // 输出结果
        for (int i = 0; i < k; i++)
            printf("%d ", path[i]);
        puts("");
    }
    
    return 0;
}

双向DFS

送礼物

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 46 + 10;

int w, n;
int g[N];
int k;
int weights[1 << 23];
int cnt;
int ans;

void dfs1(int u, int s)
{
    if (u == k)
    {
        weights[cnt++] = s;
        return ;
    }
    
    dfs1(u + 1, s);
    
    if ((LL)s + g[u] <= w)// 使用long long类型防止溢出
        dfs1(u + 1, s + g[u]);
}

void dfs2(int u, int s)
{
    if (u == n)
    {
        int l = 0, r = cnt - 1;
        while (l < r)
        {
            int mid = (l + r + 1) / 2;
            // 二分找到weights数组中,与s的和不超过w的最大的重量
            if (weights[mid] <= w - s)
                l = mid;
            else
                r = mid - 1;
        }
        
        ans = max(ans, s + weights[l]);
        
        return ;
    }
    
    dfs2(u + 1, s);
    
    if ((LL)s + g[u] <= w)// 使用long long类型防止溢出
        dfs2(u + 1, s + g[u]);
}

int main()
{
    scanf("%d %d", &w, &n);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &g[i]);
    
    // 从大到小枚举,优化搜索顺序    
    sort(g, g + n);
    reverse(g, g + n);
    
    k = n / 2;
    
    // 把前k个物品的重量打一个表
    dfs1(0, 0);
    
    // 先从小到大排序,然后使用unique函数进行去重,这是在为dfs2()中的二分做准备
    sort(weights, weights + cnt);
    cnt = unique(weights, weights + cnt) - weights;
    
    dfs2(k, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

IDA*

排书

#include <iostream>
#include <cstring>

using namespace std;

const int N = 15 + 10;

int n;
int q[N];
int w[5][N];

/*
    f()为预估代价函数,IDA*算法要求,预估代价函数的值<=真实的值(值的具体含义视情况而定)

    本题可以将任意一段序列插到序列其他部分的任意一位置,目标是要使这个序列满足单调上升,求最小步数;
    也就是说对于每一个位置上的数,它的后继一定是唯一确定的,
    而每进行一次序列插入的操作,最多可以改变3个位置上的后继;
    因此,不满足要求的后继的数量/3(上取整),是理论情况下最少的操作次数,也就是未来估计要递归的层数;
*/
int f()// 预估代价函数
{
    int cnt = 0;
    for (int i = 0; i + 1 < n; i++)
        if (q[i] + 1 != q[i + 1])
            cnt++;
    
    /*
        由于在c++中,除法计算默认是下取整的,
        因此我们使用(cnt + 2) / 3可以实现不满足要求的后继的数量/3(上取整)的效果
    */
    return (cnt + 2) / 3;
}

bool check()
{
    for (int i = 0; i + 1 < n; i++)
        if (q[i] + 1 != q[i + 1])
            return false;
            
    return true;
}

bool dfs(int depth, int max_depth)
{
    // 可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
    if (depth + f() > max_depth)
        return false;
        
    if (check())
        return true;
        
    for (int len = 1; len < n; len++)// 长度为n没有必要进行枚举,因为没有意义
        for (int l = 0; l + len - 1 < n; l++)// 枚举起点,左边界
        {
            int r = l + len - 1;// 右边界
            
            /*
                从r+1开始枚举插入操作即可,因为从r开始枚举插入操作没有意义,
                枚举往后插的情况即可,避免重复枚举
            */
            for (int k = r + 1; k < n; k++)
            {
                memcpy(w[depth], q, sizeof q);
                
                // 实现插入操作
                int x;
                int y = l;
                for (x = r + 1; x <= k; x++, y++)
                    q[y] = w[depth][x];
                for (x = l; x <= r; x++, y++)
                    q[y] = w[depth][x];
                
                if (dfs(depth + 1, max_depth))
                    return true;
                    
                memcpy(q, w[depth], sizeof q);// 恢复现场
            }
        }
        
    return false;
}

/*
    IDA*算法与A*算法类似,但相比之下更容易理解;

    IDA*算法一般配合着迭代加深算法使用;
    在dfs递归且未达到设定的最大递归层数的过程中,可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
*/
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        
        for (int i = 0; i < n; i++)
            scanf("%d", &q[i]);
            
        int k = 0;
        /*
            这个上限每次往上加1,直至在当前上限进行dfs时能找到最优解,
            并且这个上限要严格小于5,否则根据题意可知,可以停止dfs了
        */
        while (k < 5 && !dfs(0, k))
            k++;
            
        if (k >= 5)
            printf("5 or more\n");
        else
            printf("%d\n", k);
    }
    
    return 0;
}

回转游戏

/*
        A     B
        0     1
        2     3
H 4  5  6  7  8  9  10 C
        11    12
G 13 14 15 16 17 18 19 D
        20    21
        22    23
        F     E
*/
#include <iostream>

using namespace std;

int q[24];
int path[100];
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int op[8][7] = {
    {0, 2, 6, 11, 15, 20, 22},
    {1, 3, 8, 12, 17, 21, 23},
    {10, 9, 8, 7, 6, 5, 4},
    {19, 18, 17, 16, 15, 14, 13},
    {23, 21, 17, 12, 8, 3, 1},
    {22, 20, 15, 11, 6, 2, 0},
    {13, 14, 15, 16, 17, 18, 19},
    {4, 5, 6, 7, 8, 9, 10}
};

/*
    根据题意可知,每操作一次,就会在中间8个数字里面改变1个数字,
    因此,8-中间8个数字里面出现次数最多的数字的个数,是理论情况下最少的操作次数,也就是未来估计要递归的层数;
*/
int f()// 预估代价函数
{
    int sum[4] = {0};
    
    for (int i = 0; i < 8; i++)
        sum[q[center[i]]]++;
        
    int max_cnt = 0;
    for (int i = 1; i <= 3; i++)
        max_cnt = max(max_cnt, sum[i]);
        
    return 8 - max_cnt;
}

void operate(int x)// 实现操作
{
    int temp = q[op[x][0]];
    
    for (int i = 0; i <= 5; i++)
        q[op[x][i]] = q[op[x][i + 1]];
        
    q[op[x][6]] = temp;
}

bool dfs(int depth, int max_depth, int before)
{
    // 可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
    if (depth + f() > max_depth)
        return false;
        
    if (f() == 0)
        return true;
        
    for (int i = 0; i < 8; i++)
        if (opposite[i] != before)// 当前操作不能是上一次操作的反向操作,可行性剪枝
        {
            operate(i);
            
            path[depth] = i;// 记录移动步骤
            
            if (dfs(depth + 1, max_depth, i))
                return true;
                
            operate(opposite[i]);// 恢复现场
        }
        
    return false;
}

int main()
{
    while (cin >> q[0], q[0])
    {
        for (int i = 1; i < 24; i++)
            scanf("%d", &q[i]);
            
        int k = 0;
        /*
            这个上限每次往上加1,
            直至在当前上限进行dfs时能找到结果
        */
        while (!dfs(0, k, -1))
            k++;
            
        if (!k)
            printf("No moves needed");
        else
            for (int i = 0; i < k; i++)
                printf("%c", path[i] + 'A');
        puts("");
        printf("%d\n", q[6]);
    }
    
    return 0;
}

习题

池塘计数

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;
const int M = 1e3 + 10;

int n, m;
char g[N][M];
PII q[N * M];
bool st[N][M];

void bfs(int sx, int sy)
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[hh] = {sx, sy};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = t.x - 1; i <= t.x + 1; i++)
            for (int j = t.y - 1; j <= t.y + 1; j++)
            {
                if (i == sx && j == sy)
                    continue;
                if (i < 0 || i >= n || j < 0 || j >= m)
                    continue;
                if (g[i][j] == '.' || st[i][j])
                    continue;
                    
                st[i][j] = true;// 已经遍历过的,就标记一下
                
                q[++tt] = {i, j};
            }
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        scanf("%s", g[i]);
        
    int cnt = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'W' && !st[i][j])
            {
                bfs(i, j);
                cnt++;
            }
            
    // 输出结果
    printf("%d\n", cnt);
    
    return 0;
}

城堡问题

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 50 + 10;
const int M = 50 + 10;

int n, m;
int g[N][M];
PII q[N * M];
bool st[N][M];

int bfs(int sx, int sy)
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int area = 0;
    
    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        area++;
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (st[a][b])
                continue;
            if (g[t.x][t.y] >> i & 1)// 需要判断一下该方向是否有墙壁堵着
                continue;
                
            st[a][b] = true;// 已经遍历过的,就标记一下
            
            q[++tt] = {a, b};
        }
    }
    
    return area;
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            scanf("%d", &g[i][j]);
            
    int cnt = 0;
    int area = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (!st[i][j])
            {
                area = max(area, bfs(i, j));
                cnt++;
            }
    
    // 输出结果
    printf("%d\n%d\n", cnt, area);
    
    return 0;
}

山峰和山谷

#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;

int n;
int h[N][N];
PII q[N * N];
bool st[N][N];

void bfs(int sx, int sy, bool &has_higher, bool &has_lower)// 这里使用了引用类型的入参
{
    st[sx][sy] = true;// 已经遍历过的,就标记一下
    
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = t.x - 1; i <= t.x + 1; i++)
            for (int j = t.y - 1; j <= t.y + 1; j++)
            {
                if (i == t.x && j == t.y)
                    continue;
                if (i < 0 || i >= n || j < 0 || j >= n)
                    continue;
                    
                if (h[i][j] != h[t.x][t.y])
                {
                    if (h[i][j] > h[t.x][t.y])
                        has_higher = true;
                    else
                        has_lower = true;
                }
                else if (!st[i][j])
                {
                    st[i][j] = true;// 已经遍历过的,就标记一下
                    
                    q[++tt] = {i, j};
                }
            }
    }
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d\n", &h[i][j]);
            
    int peak = 0;
    int valley = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (!st[i][j])
            {
                bool has_higher = false, has_lower = false;
                
                bfs(i, j, has_higher, has_lower);
                
                if (!has_higher)
                    peak++;
                if (!has_lower)
                    valley++;
            }
            
    // 输出结果
    printf("%d %d\n", peak, valley);
    
    return 0;
}

迷宫问题

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;

int n;
int g[N][N];
PII q[N * N];
PII pre[N][N];

void bfs(int sx, int sy)
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
    
    memset(pre, -1, sizeof pre);
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= n)
                continue;
            if (g[a][b])// 判断是否为墙壁
                continue;
            if (pre[a][b].x != -1)// 判断是否已经被搜到过
                continue;
                
            pre[a][b] = t;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &g[i][j]);
            
    bfs(n - 1, n - 1);// 从迷宫的右下角开始进行bfs操作
    
    PII end = {0, 0};
    while (true)
    {
        printf("%d %d\n", end.x, end.y);
        if (end.x == n - 1 && end.y == n - 1)
            break;
        end = pre[end.x][end.y];
    }
    
    return 0;
}

武士风度的牛

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 150 + 10;
const int M = 150 + 10;

int n, m;
char g[N][M];
PII q[N * M];
int dist[N][M];

int bfs()
{
    // 先找到起点的坐标
    int sx, sy;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'K')
                sx = i, sy = j;
                
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
    int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
    
    memset(dist, -1, sizeof dist);
    dist[sx][sy] = 0;
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 8; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (g[a][b] == '*')
                continue;
            if (g[a][b] == 'H')// 题目的数据保证一定有解
                return dist[t.x][t.y] + 1;
            if (dist[a][b] != -1)// 判断是否已经被搜到过
                continue;
                
            dist[a][b] = dist[t.x][t.y] + 1;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d %d", &m, &n);
    
    for (int i = 0; i < n; i++)
        scanf("%s", &g[i]);
        
    printf("%d\n", bfs());
    
    return 0;
}

抓住那头牛

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n, k;
int q[N];
int dist[N];

int bfs()
{
    int hh = 0, tt = 0;
    q[0] = n;
    
    memset(dist, -1, sizeof dist);
    dist[n] = 0;
    
    while (hh <= tt)
    {
        int t = q[hh++];
        
        if (t == k)
            return dist[t];
            
        if (t - 1 >= 0 && dist[t - 1] == -1)
        {
            dist[t - 1] = dist[t] + 1;
            q[++tt] = t - 1;
        }
        if (t + 1 <= k && dist[t + 1] == -1)
        {
            dist[t + 1] = dist[t] + 1;
            q[++tt] = t + 1;
        }
        //  只要t*2大于k+1,我们就可以先减再乘
        if (t * 2 <= k + 1 && dist[t * 2] == -1)
        {
            dist[t * 2] = dist[t] + 1;
            q[++tt] = t * 2;
        }
    }
}

int main()
{
    scanf("%d %d", &n, &k);
    
    printf("%d\n", bfs());
    
    return 0;
}

矩阵距离

#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10;
const int M = 1e3 + 10;

int n, m;
char g[N][M];// 因为数字之间没有空格,所以用char类型读入
PII q[N * M];
int dist[N][M];

void bfs()
{
    memset(dist, -1, sizeof dist);
    
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == '1')
            {
                dist[i][j] = 0;
                q[++tt] = {i, j};
            }
            
    int dx[4] = {0, 1, 0, -1};
    int dy[4] = {1, 0, -1, 0};
    
    while (hh <= tt)
    {
        PII t = q[hh++];
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            
            if (a < 0 || a >= n || b < 0 || b >= m)
                continue;
            if (dist[a][b] != -1)// 判断是否已经被搜到过
                continue;
                
            dist[a][b] = dist[t.x][t.y] + 1;
            
            q[++tt] = {a, b};
        }
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        // 因为数字之间没有空格,所以用char类型读入
        scanf("%s", g[i]);
        
    bfs();
    
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
            printf("%d ", dist[i][j]);
        puts("");
    }
    
    return 0;
}

魔板

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>

using namespace std;

char g[2][4];
unordered_map<string, int> dist;
unordered_map<string, pair<char, string>> pre;

void set(string state)// 由当前字符串得到对应的魔板
{
    for (int j = 0; j < 4; j++)
        g[0][j] = state[j];
    for (int j = 0, i = 7; j < 4; j++, i--)
        g[1][j] = state[i];
}

string get()// 由当前魔板得到对应的字符串
{
    string res = "";
    for (int j = 0; j < 4; j++)
        res += g[0][j];
    for (int j = 3; j >= 0; j--)
        res += g[1][j];
    return res;
}

string move0(string state)// 执行操作A,交换上下两行
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    for (int j = 0; j < 4; j++)
        swap(g[0][j], g[1][j]);
        
    // 由当前魔板得到对应的字符串
    return get();
}

string move1(string state)// 执行操作B,将最右边的一列插入到最左边
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    char t1 = g[0][3], t2 = g[1][3];
    for (int i = 0; i < 2; i++)
        for (int j = 3; j >= 1; j--)
            g[i][j] = g[i][j - 1];
    g[0][0] = t1, g[1][0] = t2;
    
    // 由当前魔板得到对应的字符串
    return get();
}

string move2(string state)// 执行操作C,魔板中央对的4个数作顺时针旋转
{
    // 由当前字符串得到对应的魔板
    set(state);
    
    // 操作魔板
    char t = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = t;
    
    // 由当前魔板得到对应的字符串
    return get();
}

int bfs(string start, string end)
{
    if (start == end)
        return 0;
        
    queue<string> q;
    q.push(start);
    
    dist[start] = 0;
    
    while (!q.empty())
    {
        string t = q.front();
        q.pop();
        
        string m[3];
        m[0] = move0(t);// 执行操作A,交换上下两行
        m[1] = move1(t);// 执行操作B,将最右边的一列插入到最左边
        m[2] = move2(t);// 执行操作C,魔板中央对的4个数作顺时针旋转
        
        for (int i = 0; i < 3; i++)
            if (!dist.count(m[i]))
            {
                dist[m[i]] = dist[t] + 1;
                
                pre[m[i]] = {char(i + 'A'), t};
                
                if (m[i] == end)
                    return dist[m[i]];
                    
                q.push(m[i]);
            }
    }
}

int main()
{
    string start = "12345678";
    string end = "";
    
    for (int i = 0; i < 8; i++)
    {
        int x;
        scanf("%d", &x);
        end += char(x + '0');
    }
    
    int t = bfs(start, end);
    
    printf("%d\n", t);
    
    if (t > 0)// 如果操作序列的长度大于0,则在第二行输出字典序最小的操作序列
    {
        string op = "";
        
        while (end != start)// 从特殊状态往前追溯至基本状态
        {
            op += pre[end].first;
            end = pre[end].second;
        }
        
        // 记得要反转一下,这样才是从基本状态到特殊状态的操作序列
        reverse(op.begin(), op.end());
        
        printf("%s\n", op.c_str());// 输出字典序最小的操作序列
    }
    
    return 0;
}

电路维修

#include <iostream>
#include <cstring>
#include <deque>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 500 + 10;
const int M = 500 + 10;

int n, m;
char g[N][M];
int dist[N][M];
bool st[N][M];

int bfs()// 这里的bfs思路,本质上就是Dijkstra思路
{
    memset(st, false, sizeof st);
    
    memset(dist, 0x3f, sizeof dist);
    dist[0][0] = 0;
    
    deque<PII> q;// 双端队列
    q.push_front({0, 0});
    
    int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
    int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};
    
    // 根据四个方向偏移,所能走到的四种电路的形状,分别为:左上\,右上/,右下\,左下/
    char cs[4] = {'\\', '/', '\\', '/'};
    
    while (!q.empty())
    {
        PII t = q.front();
        q.pop_front();
        
        if (t.x == n && t.y == m)
            return dist[t.x][t.y];
            
        if (st[t.x][t.y])
            continue;
            
        st[t.x][t.y] = true;
        
        for (int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a > n || b < 0 || b > m)
                continue;
                
            int ia = t.x + ix[i], ib = t.y + iy[i];
            
            int d = dist[t.x][t.y] + (g[ia][ib] != cs[i]);
            
            if (d < dist[a][b] && dist[a][b] == 0x3f3f3f3f)
            {
                dist[a][b] = d;
                
                if (g[ia][ib] != cs[i])
                    q.push_back({a, b});
                else
                    q.push_front({a, b});
            }
            if (d < dist[a][b] && dist[a][b] != 0x3f3f3f3f)
            {
                dist[a][b] = d;
                
                q.push_front({a, b});
            }
        }
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d %d", &n, &m);
        
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);

        if ((n + m) & 1)// 通过观察可以知道,当终点的横纵坐标的和为奇数时,则起点永远无法连通终点
            printf("NO SOLUTION\n");
        else
            printf("%d\n", bfs());
    }
    
    return 0;
}

字串变换

#include <iostream>
#include <queue>
#include <unordered_map>

using namespace std;

const int N = 6;

string a, b;
int n;
string oa[N], ob[N];

int extend(
    queue<string>& qa,
    unordered_map<string, int>& da,
    unordered_map<string, int>& db,
    string oa[],
    string ob[]
)
{
    int d = da[qa.front()];
    
    while (qa.size() && da[qa.front()] == d)// 扩展完整一层
    {
        string t = qa.front();
        qa.pop();
        
        for (int i = 0; i < n; i++)// 枚举规则
            for (int j = 0; j < t.size(); j++)// 枚举当前字符串的每个位置
                // 判断当前字符串的当前位置是否符合当前规则进行变换
                if (t.substr(j, oa[i].size()) == oa[i])
                {
                    // 变换后的字符串
                    string u = t.substr(0, j) + ob[i] + t.substr(j + oa[i].size());
                    
                    if (da.count(u))// 判断从出发的这一边是否已经搜索过
                        continue;
                        
                    if (db.count(u))// 如果在另一边已经搜索过,则表示从出发的这一边可以变换到另一边
                        return da[t] + db[u] + 1;
                        
                    qa.push(u);
                    
                    da[u] = da[t] + 1;
                }
    }
    
    // 当扩展完整一层后,还没有找到结果的话,则继续操作下一层
    return 11;
}

int bfs()// 双向bfs
{
    if (a == b)
        return 0;
        
    queue<string> qa, qb;
    qa.push(a), qb.push(b);
    
    unordered_map<string, int> da, db;
    da[a] = 0, db[b] = 0;
    
    int step = 0;// 记录变换步数
    
    while (qa.size() && qb.size())
    {
        int t;
        
        // 每次扩展时,选择队列中数量较少的一边来进行扩展,每次每边扩展完整一层
        if (qa.size() <= qb.size())
            t = extend(qa, da, db, oa, ob);
        else
            t = extend(qb, db, da, ob, oa);
        
        if (t <= 10)
            return t;
            
        step++;
        
        // 当变换步数大于等于10步时,还没有找到结果的话,则可以结束循环了
        if (step >= 10)
            break;
    }
    
    return -1;
}

int main()
{
    cin >> a >> b;
    
    while (cin >> oa[n] >> ob[n])// 输入规则
        n++;
        
    int t = bfs();// 双向bfs
    
    if (t == -1)
        printf("NO ANSWER!\n");
    else
        printf("%d\n", t);
        
    return 0;
}

第K短路

#include <iostream>
#include <cstring>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
typedef pair<int, PII> PIPII;

const int N = 1e3 + 10;
const int M = 2 * 1e4 + 10;// 因为要反向建图,所以用到的边数需要乘2

int n, m;
int h[N], rh[N], w[M], e[M], ne[M], idx;
int s, t, k;
int dist[N];
int cnt[N];

void add(int temp[], int a, int b, int c)
{
    w[idx] = c;
    e[idx] = b;
    ne[idx] = temp[a];
    temp[a] = idx++;
}

// 预估代价函数
void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[t] = 0;
    
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, t});
    
    while (!q.empty())
    {
        PII temp = q.top();
        q.pop();
        
        int ver = temp.y;
        int distance = temp.x;
        
        for (int i = rh[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                q.push({dist[j], j});
            }
        }
    }
}

int astar()
{
    priority_queue<PIPII, vector<PIPII>, greater<PIPII>> q;// 优先队列小根堆
    /*
        优先队列小根堆中每个状态的权重是:
            当前代价 + 预估代价
            (其中,
            当前代价为从起点到该状态的真实距离(注意并不一定是最短距离),
            预估代价为从该状态到终点的估计距离,估计距离一般通过预估代价函数得到)
    */
    q.push({0 + dist[s], {0, s}});
    
    while (!q.empty())
    {
        PIPII temp = q.top();
        q.pop();
        
        int ver = temp.y.y;
        
        cnt[ver]++;// 计算当前点第几次弹出优先队列小根堆
        
        if (ver == t && cnt[ver] == k)
            return temp.y.x;
            
        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            
            /*
                可以对最短路径的搜索进行优化,
                每个点第几次弹出优先队列小根堆就是第几短路径,
                当某个点第k次弹出优先队列小根堆,准备要进行第k+1次进入优先队列小根堆时,
                说明该点到终点的路径一定不会是前k短路径。
                
                这个优化,可以把起点和终点不连通的情况也考虑到,防止TLE(Time Limit Exceeded)
            */
            if (cnt[j] < k)
                q.push({temp.y.x + w[i] + dist[j], {temp.y.x + w[i], j}});
        }
    }
    
    return -1;
}

int main()
{
    scanf("%d %d", &n, &m);
    
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        
        add(h, a, b, c);
        add(rh, b, a, c);// 反向建图的目的是为了反向做一次dijkstra操作,充当了预估代价函数的作用
    }
    
    scanf("%d %d %d", &s, &t, &k);
    
    /*
        当起点和终点相同时,
        k中隐含了一条长度为0的没有边的最短路,
        但这条最短路是不对的,
        因为题目要求起点和终点之间至少包含一条边,
        所以k++,是为了排除掉长度为0的最短路
    */
    if (s == t)
        k++;
        
    dijkstra();
    
    printf("%d\n", astar());
    
    return 0;
}

八数码

#include <iostream>
#include <unordered_map>
#include <queue>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<string, char> PSC;
typedef pair<int, string> PIS;

// 预估代价函数
int f(string state)
{
    int cnt = 0;
    
    for (int i = 0; i < state.size(); i++)
        if (state[i] != 'x')
        {
            int t = state[i] - '1';
            cnt += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
        }
    
    // 返回估计距离,值为当前状态中的,每个数的位置与它对应的目标位置的曼哈顿距离,的总和
    return cnt;
}

string bfs(string start)
{
    string end = "12345678x";
    
    unordered_map<string, int> dist;
    unordered_map<string, PSC> prev;
    priority_queue<PIS, vector<PIS>, greater<PIS>> q;// 优先队列小根堆
    
    dist[start] = 0;
    /*
        优先队列小根堆中每个状态的权重是:
            当前代价 + 预估代价
            (其中,
            当前代价为从起点到该状态的真实距离(注意并不一定是最短距离),
            预估代价为从该状态到终点的估计距离,估计距离一般通过预估代价函数得到)
    */
    q.push({0 + f(start), start});
    
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    char op[4] = {'u', 'r', 'd', 'l'};
    
    while (!q.empty())
    {
        PIS t = q.top();
        q.pop();
        
        string state = t.y;
        
        // 当终点首次弹出时,则一定是最优解,然后退出循环即可
        if (state == end)
            break;
        
        int x, y;
        for (int i = 0; i < state.size(); i++)
            if (state[i] == 'x')
            {
                x = i / 3;
                y = i % 3;
            }
            
        string source = state;
        
        for (int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            
            if (a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                // 把当前状态的网格转换成新状态的网格
                swap(state[x * 3 + y], state[a * 3 + b]);
                
                // 注意,在A*算法中,当第一次搜到某个点的时候,距离不一定是最短的,其dist[]的值是不断变小的,所以要加后一个判断
                if (!dist.count(state) || dist[state] > dist[source] + 1)
                {
                    // 更新当前状态的当前代价
                    dist[state] = dist[source] + 1;
                    
                    // 更新优先队列小根堆
                    q.push({dist[state] + f(state), state});
                    
                    // 记录当前状态是从哪个状态走过来的,且对应哪个操作
                    prev[state] = {source, op[i]};
                }
                
                // 状态恢复,继续遍历当前这个点(x,y)可以往哪个方向走
                swap(state[x * 3 + y], state[a * 3 + b]);
            }
        }
    }
    
    // 得到操作方案
    string res;
    while (end != start)
    {
        res += prev[end].y;
        end = prev[end].x;
    }
    reverse(res.begin(), res.end());// 记得要反转一下
    
    return res;
}

int main()
{
    string g, c, seq;
    
    while (cin >> c)
    {
        g += c;
        if (c != "x")
            seq += c;
    }
    
    // 计算逆序对的数量
    int t = 0;
    for (int i = 0; i < seq.size(); i++)
        for (int j = i + 1; j < seq.size(); j++)
            if (seq[i] > seq[j])
                t++;
    
    if (t % 2)
        // 在八数码问题中,逆序对的数量为奇数时,则八数码问题没有解
        printf("unsolvable\n");
    else
        printf("%s\n", bfs(g).c_str());
        
    return 0;
}

迷宫

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100 + 10;

int n;
char g[N][N];
int xa, ya, xb, yb;
bool st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool dfs(int x, int y)
{
    if (g[x][y] == '#')
        return false;
            
    // 判断是否找到终点
    if (x == xb && y == yb)
        return true;
        
    st[x][y] = true;
    
    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)
            continue;
        if (st[a][b])
            continue;
            
        if (dfs(a, b))
            return true;
    }
    
    return false;
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d", &n);
        
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);
            
        scanf("%d %d %d %d", &xa, &ya, &xb, &yb);
        
        memset(st, false, sizeof st);
        
        printf("%s\n", dfs(xa, ya) ? "YES" : "NO");
    }
    
    return 0;
}

红与黑

#include <iostream>
#include <cstring>

using namespace std;

const int N = 20 + 10;
const int M = 20 + 10;

int n, m;
char g[N][M];
bool st[N][M];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)
{
    int cnt = 1;
    
    st[x][y] = true;
    
    for (int i = 0; i < 4; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        
        if (a < 0 || a >= n || b < 0 || b >= m)
            continue;
        if (g[a][b] == '#')
            continue;
        if (st[a][b])
            continue;
            
        cnt += dfs(a, b);
    }
    
    return cnt;
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; i++)
            scanf("%s", g[i]);
            
        int a, b;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (g[i][j] == '@')
                    a = i, b = j;
                    
        memset(st, false, sizeof st);
        
        printf("%d\n", dfs(a, b));
    }
    
    return 0;
}

马走日

#include <iostream>

using namespace std;

const int N = 10;
const int M = 10;

int n, m;
int x, y;
int ans;
bool st[N][M];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

void dfs(int x, int y, int cnt)
{
    if (cnt == n * m)
    {
        ans++;
        return ;
    }
    
    st[x][y] = true;
    
    for (int i = 0; i < 8; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        
        if (a < 0 || a >= n || b < 0 || b >= m)
            continue;
        if (st[a][b])
            continue;
            
        dfs(a, b, cnt + 1);
    }
    
    st[x][y] = false;// 恢复现场
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while (t--)
    {
        scanf("%d %d %d %d", &n, &m, &x, &y);
        
        ans = 0;
        
        dfs(x, y, 1);
        
        printf("%d\n", ans);
    }
    
    return 0;
}

单词接龙

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 20;

int n;
string word[N];
int g[N][N];
int ans;
int used[N];

void dfs(string dragon, int before)
{
    /*
        在C++中,string类型的size()函数的返回值是一个无符号类型的值
        在C++中,max()函数的两个入参的类型必须要保持一致
    */
    ans = max(ans, (int)dragon.size());
    
    used[before]++;
    
    for (int j = 0; j < n; j++)
        if (g[before][j] && used[j] < 2)
            dfs(dragon + word[j].substr(g[before][j]), j);

    used[before]--;// 恢复现场
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        cin >> word[i];
        
    char start;
    cin >> start;
    
    // 预处理
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            string a = word[i];
            string b = word[j];
            
            // 我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度
            for (int k = 1; k < min(a.size(), b.size()); k++)
                if (a.substr(a.size() - k, k) == b.substr(0, k))
                {
                    g[i][j] = k;
                    break;
                }
        }
        
    for (int i = 0; i < n; i++)
        if (word[i][0] == start)
            dfs(word[i], i);
            
    printf("%d\n", ans);
    
    return 0;
}

知识点补充

  • 在C++中,string类型的size()函数的返回值是一个无符号类型的值
  • 在C++中,max()函数的两个入参的类型必须要保持一致

分成互质组

#include <iostream>

using namespace std;

const int N = 10;

int n;
int a[N];
int ans = N;// 最坏情况下是,每个数单独一个组
bool st[N];
int group[N][N];

// 欧几里得算法求两个数的最大公约数
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

bool check(int group[], int gc, int b)
{
    for (int i = 0; i < gc; i++)
        // 如果两个数的最大公约数大于1,则两个数不互质
        if (gcd(a[group[i]], b) > 1)
            return false;
            
    return true;
}

/*
    四个参数的含义分别为:
        当前搜索到第几组、
        当前组中一共有几个数、
        一共搜索过几个数、
        从第几个数(具体数值为a数组的下标)开始进行搜索
*/
void dfs(int g, int gc, int tc, int start)// 这个start可以减掉很多组内的等效冗余的方案
{
    if (g >= ans)
        return ;
        
    if (tc == n)
        ans = g;
        
    bool flag = true;
    
    for (int i = start; i < n; i++)
        // 判断当前a[i]这个数是否被用过,并且,当前a[i]这个数,是否和当前组g中的一共gc个数都互质
        if (!st[i] && check(group[g], gc, a[i]))
        {
            st[i] = true;
            
            // 把当前a[i]这个数加入到当前组g中,然后继续进行dfs操作。这个操作可以不恢复现场
            group[g][gc] = i;
            
            dfs(g, gc + 1, tc + 1, i + 1);
            
            st[i] = false;// 恢复现场
            
            // 如果至少有一个数可以放入到当前组中,则不需要新开一个新的组来进行dfs操作
            flag = false;
            
            /*
                剪枝操作,
                因为开一个新组的前提条件是每个数都不能放到旧组中了,
                所以剩下的第一个数一定会放到某个新组中,那我们可以将它固定到第一个新组中,
                这样可以减掉很多组与组之间的等效冗余的方案
            */
            if (gc == 0)
                return ;
        }
    
    // 如果至少有一个数可以放入到当前组中,则不需要新开一个新的组来进行dfs操作
    if (flag)
        dfs(g + 1, 0, tc, 0);
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    
    // 这个dfs的搜索顺序本质上是,按照每个组的每个位置进行搜索的!!!
    dfs(1, 0, 0, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

小猫爬山

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 18;

int n, m;
int w[N];
int ans = N;// 最坏情况下是,每只猫单独一辆缆车
int sum[N];

void dfs(int u, int k)
{
    if (k >= ans)// 最优性剪枝
        return ;
        
    if (u == n)
        ans = k;
        
    for (int i = 0; i < k; i++)
        if (sum[i] + w[u] <= m)// 可行性剪枝
        {
            sum[i] += w[u];
            dfs(u + 1, k);
            sum[i] -= w[u];// 恢复现场
        }
    
    /*
        这里并不能使用贪心,举出例子:
        请尝试在maxV=16时,重量为9 5 5 5 4 3
        贪心结果是9+5 5+5+4 3,结果为3
        但正确结果为9+4+3 5+5+5,结果为2
    */
    
    // 新开一辆缆车
    sum[k] = w[u];
    dfs(u + 1, k + 1);
    sum[k] = w[u];// 恢复现场,这个现场不恢复也不影响最终答案
}

int main()
{
    scanf("%d %d", &n, &m);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &w[i]);
    
    // 优化搜索顺序
    sort(w, w + n);
    reverse(w, w + n);
    
    // 这个dfs的搜索顺序本质上是,按照每个小猫能放到哪个缆车上进行搜索的!!!
    dfs(0, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

数独

#include <iostream>

using namespace std;

const int N = 9;

char str[100];
int row[N], col[N], cell[3][3];
int ones[1 << N];
int map[1 << N];

// 初始化处理,先默认每一行、每一列、每一个3×3的九宫格都可以不重复地填数字1~9
void init()
{
    for (int i = 0; i < N; i++)
        row[i] = col[i] = (1 << N) - 1;
    
    // 一共有9个3x3的九宫格
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            cell[i][j] = (1 << N) - 1;
}

// 处理第x行、第y列、(x,y)所属的3×3的九宫格,可行性剪枝
void draw(int x, int y, int t, int flag)
{
    if (flag)
        str[x * N + y] = '1' + t;
    else
        str[x * N + y] = '.';
        
    int v = 1 << t;
    
    if (!flag)
        v = 0 - 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 + 1);
}

bool dfs(int cnt)
{
    if (!cnt)
        return true;
        
    // 优化搜索顺序,让dfs的搜索顺序从,可以选择填写的数字个数较少的位置,开始进行搜索
    int minv = 10;
    int x, y;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (str[i * N + j] == '.')
            {
                int state = get(i, j);
                
                if (ones[state] < minv)
                {
                    minv = ones[state];
                    x = i, y = j;
                }
            }
            
    // 优化搜索顺序,让dfs的搜索顺序从,可以选择填写的数字的个数较少的位置,开始进行搜索
    int state = get(x, y);
    for (int i = state; i; i -= lowbit(i))
    {
        int t = map[lowbit(i)];
        
        draw(x, y, t, true);
        
        if (dfs(cnt - 1))
            return true;
            
        draw(x, y, t, false);// 恢复现场
    }
    
    // 当state等于0时,说明已经出现了,存在有一个位置无法填写数字,的情况,即可返回false
    return false;
}

int main()
{
    // 位运算优化
    for (int i = 0; i < (1 << N); i++)
        for (int j = 0; j < N; j++)
            ones[i] += (i >> j) & 1;
    
    for (int i = 0; i < N; i++)
        map[1 << i] = i;
        
    while (cin >> str, str[0] != 'e')
    {
        init();// 初始化处理,先默认每一行、每一列、每一个3×3的九宫格都可以不重复地填数字1~9
        
        int cnt = 0;
        
        for (int i = 0; i < N; i++)
            for (int j = 0; j < N; j++)
                if (str[i * N + j] != '.')
                {
                    int t = str[i * N + j] - '1';
                    
                    // 处理第i行、第j列、(i,j)所属的3×3的九宫格,可行性剪枝
                    draw(i, j, t, true);
                }
                else
                    cnt++;
                    
        if (dfs(cnt))// 题目保证输入中的每个谜题都只有一个解决方案
            cout << str << endl;
    }
    
    return 0;
}

木棒

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 70;

int n;
int w[N];
int sum;
int length;
bool st[N];

/*
    三个参数的含义分别为:
        一共搜索过几个木棒、
        当前木棒的长度、
        从第几个木棍(具体数值为w数组的下标)开始进行搜索
*/
bool dfs(int u, int ul, int start)// 这个start可以减掉很多组内的等效冗余的方案,这是一个优化
{
    if (u * length == sum)
        return true;
        
    if (ul == length)
        return dfs(u + 1, 0, 0);
        
    for (int i = start; i < n; i++)
    {
        // 可行性剪枝
        if (st[i] || ul + w[i] > length)
            continue;
            
        st[i] = true;
        
        if (dfs(u, ul + w[i], i + 1))
            return true;
            
        st[i] = false;// 恢复现场
        
        // 如果当前木棒放第一根木棍导致后续方案失败,则整个方案一定失败,这是一个优化
        if (!ul)
            return false;
            
        // 如果当前木棒放最后一根木棍可以让当前这根木棒凑成length,但是导致后续方案失败,则整个方案一定失败,这是一个优化
        if (ul + w[i] == length)
            return false;
            
        // 如果当前木棍放到当前木棒中失败了,则直接略过后面所有长度相等的木棍,这是一个优化
        int j = i;
        while (j < n && w[j] == w[i])
            j++;
        i = j - 1;
    }
    
    return false;
}

int main()
{
    while (cin >> n, n)
    {
        memset(st, false, sizeof st);
        
        sum = 0;
        
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &w[i]);
            sum += w[i];
        }
        
        // 优化搜索顺序
        sort(w, w + n);
        reverse(w, w + n);
        
        length = w[0];// 木棒的可能最小长度,可以从最长的木棍开始进行枚举,这是一个优化
        
        while (true)
        {
            // 因为木棒是等长的,因此必须要满足sum % length == 0,这是一个优化
            if (sum % length == 0 && dfs(0, 0, 0))
            {
                printf("%d\n", length);
                break;
            }
            
            length++;
        }
    }
    
    return 0;
}

生日蛋糕

#include <iostream>
#include <cmath>

using namespace std;

const int M = 25;

int n, m;
int r[M], h[M];
int ans;
int minv[M], mins[M];

void dfs(int u, int v, int s)
{
    /*
        当已经消耗了的体积v加上前u层最小的体积都大于给定的体积n时,
        表示不合法,
        即可结束dfs操作,
        可行性剪枝
    */
    if (v + minv[u] > n)
        return ;
    /*
        当已经消耗了的表面积s加上前u层最小的侧表面积都大于等于结果ans时,
        表示结果已经无法再进行优化,
        即可结束dfs操作,
        最优性剪枝
    */
    if (s + mins[u] >= ans)
        return ;
    
    /*
        设当前搜索到第u层,已经消耗了体积v,已经消耗了表面积s,
        即有n - v = r[1] * r[1] * h[1] + r[2] * r[2] * h[2] + ...... + r[u] * r[u] * h[u],
        然后设前u层的侧表面积为S[u],
        即有S[u] = 2 * r[1] * h[1] + 2 * r[2] * h[2] + ...... + 2 * r[u] * h[u]
                 = 2 * (r[1] * h[1] + r[2] * h[2] + ...... + r[u] * h[u])
                 = 2 / r[u + 1] * (r[1] * h[1] + r[2] * h[2] + ...... + r[u] * h[u]) * r[u + 1]
                 = 2 / r[u + 1] * (r[1] * r[u + 1] * h[1] + r[2] * r[u + 1] * h[2] + ...... + r[u] * r[u + 1] * h[u]),
        可得:
            S[u] >= 2 / r[u + 1] * (r[1] * r[1] * h[1] + r[2] * r[2] * h[2] + ...... + r[u] * r[u] * h[u])
        即有S[u] >= 2 / r[u + 1] * (n - v),
        即我们发现了前u层的侧表面积的下界为2 / r[u + 1] * (n - v),
        当已经消耗了的表面积s加上前u层的侧表面积的下界都大于等于结果ans时,
        表示结果已经无法再进行优化,
        即可结束dfs操作,
        最优性剪枝
    */
    if (s + 2 * (n - v) / r[u + 1] >= ans)// 最优性剪枝
        return ;
    
    if (!u)
    {
        if (v == n)
            ans = s;
            
        return ;
    }
    
    // 先搜半径r,再搜高度h,因为半径r会涉及到平方级别(圆柱体的体积为:r * r * h),从大到小搜,优化搜索顺序
    /*
        设当前搜索到第u层,已经消耗了体积v,
        因为每一层的高度和半径至少为1且每一层的高度和半径自底向上是递减的,
        所以就有:
            u <= r[u] <= r[u + 1] - 1
            u <= h[u] <= h[u + 1] - 1
        同时还要保证体积不超过n,
        即有r[u] * r[u] * h[u] <= n - v,可得:
            r[u] <= sqrt((n - v) / h[u]),其中h[u]的最小值为u,即有r[u] <= sqrt((n - v) / u)
            h[u] <= (n - v) / r[u] / r[u]
        然后两个上界取最小,
        可行性剪枝
    */
    for (int i = min(r[u + 1] - 1, (int)sqrt((n - v) / u)); i >= u; i--)// 在C++中,min()函数的两个入参的类型必须要保持一致
        for (int j = min(h[u + 1] - 1, (n - v) / i / i); j >= u; j--)
        {
            int t = 0;
            if (u == m)
                // 只有当搜到第m层时,才会把俯视视角所看到的蛋糕的表面积加上
                t = i * i;
            
            r[u] = i, h[u] = j;
            
            dfs(u - 1, v + i * i * j, s + 2 * i * j + t);
        }
}

// 注意!!!根据题意可知,我们在计算中不涉及到π,相当于可以把π忽略
int main()
{
    scanf("%d %d", &n, &m);
    
    // 预处理,假设第一层半径和高度为1,第二层半径和高度为2,第三层半径和高度为3,......,第m层半径和高度为m
    for (int i = 1; i <= m; i++)
    {
        minv[i] = minv[i - 1] + i * i * i;// 前u层最小的体积
        mins[i] = mins[i - 1] + 2 * i * i;// 前u层最小的侧表面积
    }
    
    r[m + 1] = h[m + 1] = 0x3f3f3f3f;
    ans = 0x3f3f3f3f;
    
    /*
        我们这里设最上面一层为第1层,即半径和高度最小的一层,
        最下面一层为第m层,即半径和高度最大的一层,
        自底向上搜,优化搜索顺序
    */
    dfs(m, 0, 0);
    
    if (ans != 0x3f3f3f3f)
        printf("%d\n", ans);
    else
        printf("0\n");
    
    return 0;
}

知识点补充

  • 在C++中,min()函数的两个入参的类型必须要保持一致

加成序列

#include <iostream>

using namespace std;

const int N = 100 + 10;

int n;
int path[N];

bool dfs(int u, int depth)
{
    if (u == depth)
        return path[u - 1] == n;
    
    bool st[N] = {false};// 将st数组的值全部初始化成false
    
    // 从大到小枚举,优化搜索顺序
    for (int i = u - 1; i >= 0; i--)
        for (int j = i; j >= 0; j--)
        {
            int s = path[i] + path[j];
            
            // 可行性剪枝
            if (s > n)
                continue;
            
            // 可行性剪枝
            if (s <= path[u - 1])
                continue;
            
            // 这个st数组可以减掉很多组内的等效冗余的方案
            if (st[s])
                continue;
                
            st[s] = true;
            
            path[u] = s;
            
            if (dfs(u + 1, depth))
                return true;
        }
        
    return false;
}

int main()
{
    path[0] = 1;// 根据题意可知
    
    while (cin >> n, n)
    {
        int k = 1;
        /*
            这个上限每次往上加1,
            直至在当前上限进行dfs时能找到最优解,
            这种dfs求最小值的方法被称为迭代加深求最小值
        */
        while (!dfs(1, k))
            k++;
        
        // 输出结果
        for (int i = 0; i < k; i++)
            printf("%d ", path[i]);
        puts("");
    }
    
    return 0;
}

送礼物

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 46 + 10;

int w, n;
int g[N];
int k;
int weights[1 << 23];
int cnt;
int ans;

void dfs1(int u, int s)
{
    if (u == k)
    {
        weights[cnt++] = s;
        return ;
    }
    
    dfs1(u + 1, s);
    
    if ((LL)s + g[u] <= w)// 使用long long类型防止溢出
        dfs1(u + 1, s + g[u]);
}

void dfs2(int u, int s)
{
    if (u == n)
    {
        int l = 0, r = cnt - 1;
        while (l < r)
        {
            int mid = (l + r + 1) / 2;
            // 二分找到weights数组中,与s的和不超过w的最大的重量
            if (weights[mid] <= w - s)
                l = mid;
            else
                r = mid - 1;
        }
        
        ans = max(ans, s + weights[l]);
        
        return ;
    }
    
    dfs2(u + 1, s);
    
    if ((LL)s + g[u] <= w)// 使用long long类型防止溢出
        dfs2(u + 1, s + g[u]);
}

int main()
{
    scanf("%d %d", &w, &n);
    
    for (int i = 0; i < n; i++)
        scanf("%d", &g[i]);
    
    // 从大到小枚举,优化搜索顺序    
    sort(g, g + n);
    reverse(g, g + n);
    
    k = n / 2;
    
    // 把前k个物品的重量打一个表
    dfs1(0, 0);
    
    // 先从小到大排序,然后使用unique函数进行去重,这是在为dfs2()中的二分做准备
    sort(weights, weights + cnt);
    cnt = unique(weights, weights + cnt) - weights;
    
    dfs2(k, 0);
    
    printf("%d\n", ans);
    
    return 0;
}

排书

#include <iostream>
#include <cstring>

using namespace std;

const int N = 15 + 10;

int n;
int q[N];
int w[5][N];

/*
    f()为预估代价函数,IDA*算法要求,预估代价函数的值<=真实的值(值的具体含义视情况而定)

    本题可以将任意一段序列插到序列其他部分的任意一位置,目标是要使这个序列满足单调上升,求最小步数;
    也就是说对于每一个位置上的数,它的后继一定是唯一确定的,
    而每进行一次序列插入的操作,最多可以改变3个位置上的后继;
    因此,不满足要求的后继的数量/3(上取整),是理论情况下最少的操作次数,也就是未来估计要递归的层数;
*/
int f()// 预估代价函数
{
    int cnt = 0;
    for (int i = 0; i + 1 < n; i++)
        if (q[i] + 1 != q[i + 1])
            cnt++;
    
    /*
        由于在c++中,除法计算默认是下取整的,
        因此我们使用(cnt + 2) / 3可以实现不满足要求的后继的数量/3(上取整)的效果
    */
    return (cnt + 2) / 3;
}

bool check()
{
    for (int i = 0; i + 1 < n; i++)
        if (q[i] + 1 != q[i + 1])
            return false;
            
    return true;
}

bool dfs(int depth, int max_depth)
{
    // 可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
    if (depth + f() > max_depth)
        return false;
        
    if (check())
        return true;
        
    for (int len = 1; len < n; len++)// 长度为n没有必要进行枚举,因为没有意义
        for (int l = 0; l + len - 1 < n; l++)// 枚举起点,左边界
        {
            int r = l + len - 1;// 右边界
            
            /*
                从r+1开始枚举插入操作即可,因为从r开始枚举插入操作没有意义,
                枚举往后插的情况即可,避免重复枚举
            */
            for (int k = r + 1; k < n; k++)
            {
                memcpy(w[depth], q, sizeof q);
                
                // 实现插入操作
                int x;
                int y = l;
                for (x = r + 1; x <= k; x++, y++)
                    q[y] = w[depth][x];
                for (x = l; x <= r; x++, y++)
                    q[y] = w[depth][x];
                
                if (dfs(depth + 1, max_depth))
                    return true;
                    
                memcpy(q, w[depth], sizeof q);// 恢复现场
            }
        }
        
    return false;
}

/*
    IDA*算法与A*算法类似,但相比之下更容易理解;

    IDA*算法一般配合着迭代加深算法使用;
    在dfs递归且未达到设定的最大递归层数的过程中,可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
*/
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        
        for (int i = 0; i < n; i++)
            scanf("%d", &q[i]);
            
        int k = 0;
        /*
            这个上限每次往上加1,直至在当前上限进行dfs时能找到最优解,
            并且这个上限要严格小于5,否则根据题意可知,可以停止dfs了
        */
        while (k < 5 && !dfs(0, k))
            k++;
            
        if (k >= 5)
            printf("5 or more\n");
        else
            printf("%d\n", k);
    }
    
    return 0;
}

回转游戏

/*
        A     B
        0     1
        2     3
H 4  5  6  7  8  9  10 C
        11    12
G 13 14 15 16 17 18 19 D
        20    21
        22    23
        F     E
*/
#include <iostream>

using namespace std;

int q[24];
int path[100];
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int op[8][7] = {
    {0, 2, 6, 11, 15, 20, 22},
    {1, 3, 8, 12, 17, 21, 23},
    {10, 9, 8, 7, 6, 5, 4},
    {19, 18, 17, 16, 15, 14, 13},
    {23, 21, 17, 12, 8, 3, 1},
    {22, 20, 15, 11, 6, 2, 0},
    {13, 14, 15, 16, 17, 18, 19},
    {4, 5, 6, 7, 8, 9, 10}
};

/*
    根据题意可知,每操作一次,就会在中间8个数字里面改变1个数字,
    因此,8-中间8个数字里面出现次数最多的数字的个数,是理论情况下最少的操作次数,也就是未来估计要递归的层数;
*/
int f()// 预估代价函数
{
    int sum[4] = {0};
    
    for (int i = 0; i < 8; i++)
        sum[q[center[i]]]++;
        
    int max_cnt = 0;
    for (int i = 1; i <= 3; i++)
        max_cnt = max(max_cnt, sum[i]);
        
    return 8 - max_cnt;
}

void operate(int x)// 实现操作
{
    int temp = q[op[x][0]];
    
    for (int i = 0; i <= 5; i++)
        q[op[x][i]] = q[op[x][i + 1]];
        
    q[op[x][6]] = temp;
}

bool dfs(int depth, int max_depth, int before)
{
    // 可以对未来的递归层数做出判断,便于提前剪枝,优化时间复杂度;
    if (depth + f() > max_depth)
        return false;
        
    if (f() == 0)
        return true;
        
    for (int i = 0; i < 8; i++)
        if (opposite[i] != before)// 当前操作不能是上一次操作的反向操作,可行性剪枝
        {
            operate(i);
            
            path[depth] = i;// 记录移动步骤
            
            if (dfs(depth + 1, max_depth, i))
                return true;
                
            operate(opposite[i]);// 恢复现场
        }
        
    return false;
}

int main()
{
    while (cin >> q[0], q[0])
    {
        for (int i = 1; i < 24; i++)
            scanf("%d", &q[i]);
            
        int k = 0;
        /*
            这个上限每次往上加1,
            直至在当前上限进行dfs时能找到结果
        */
        while (!dfs(0, k, -1))
            k++;
            
        if (!k)
            printf("No moves needed");
        else
            for (int i = 0; i < k; i++)
                printf("%c", path[i] + 'A');
        puts("");
        printf("%d\n", q[6]);
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值