acwing算法提高课第二章搜索

第二章 搜索

包括Flood Fill、最短路模型、多源BFS、最小步数模型、双端队列广搜、双向广搜、A*、DFS之连通性模型、DFS之搜索顺序、DFS之剪枝与
优化、迭代加深、双向DFS、IDA*等内容

Flood Fill

Flood Fill 算法,可以在线性时间复杂度内,找到某个点所在的联通块,BFS不会爆栈,DFS可能会爆栈,能用宽搜就用宽搜

AcWing 1097. 池塘计数

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second


using namespace std;

typedef pair<int, int> PII;


const int N = 110,M=N*N;

int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};

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

void bfs(int sx,int sy)
{
    //数组模拟队列
    int hh=0,tt=0;
    q[0]={sx,sy};//起点,最开始队列只有一个元素
    st[sx][sy]=true;
    
    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]=='.'||st[a][b])continue;
           
            q[++tt]={a,b};
            st[a][b]=true;
        }
        
    }
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
       for (int j = 0; j < m; j ++ )
          cin>>g[i][j];
          
    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++;
          }
    cout<<cnt<<endl;
    return 0;
}

AcWing 1098. 城堡问题

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second


using namespace std;
typedef pair<int, int> PII;
const int N = 55,M=N*N;
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};//西,北,东,南

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

int bfs(int sx,int sy)
{
    int hh=0,tt=0;
    int area=0;
    q[0]={sx,sy};
    st[sx][sy]=true;
    
    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;
            
            q[++tt]={a,b};
            st[a][b]=true;
        }
    }
    return area;
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
       for(int j=0;j<m;j++)
          cin>>g[i][j];
          
    int cnt=0,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++;
           }
       
    cout<<cnt<<endl;
    cout<<area<<endl;
    
    return 0;
}

AcWing 1106. 山峰和山谷

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second

using namespace std;
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};

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

void bfs(int sx, int sy, bool& has_higher, bool& has_lower) {
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    st[sx][sy] = true;

    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 >= n) continue;
            if (h[a][b] != h[t.x][t.y]) {
                if (h[a][b] > h[t.x][t.y]) has_higher = true;
                else has_lower = true;
            } else if (!st[a][b]) {
                q[++tt] = {a, b};
                st[a][b] = true;
            }
        }
    }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
       for (int j = 0; j < n; j++)
          cin >> h[i][j];
          
    int peak = 0, 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++;
           }
       
    cout << peak <<endl;
    cout<< valley << endl;
   
    return 0;
}
// 只要周围没有比他低的,那么他就是山谷
// 只要周围没有比他高的,那么他就是山峰
// 如果既有比他高的,又有比他矮的,那么他就什么都不是
// 统计一下每个联通块,如果周围比他高,就标记一下,如果周围比他矮,也标记一下

最短路模型

宽搜的特点:当所有边的权重都相等的时候,从起点开始搜,就能找到最短的路径

AcWing 1076. 迷宫问题

输出最短的路径

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N =1010,M=N*N;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int n;
int g[N][N];
PII q[M];
PII pre[N][N];//上一步的路径用一个pair来存

void bfs(int sx,int sy)
{
    int hh=0,tt=0;
    q[0]={sx,sy};
    
    memset(pre, -1, sizeof pre);
    pre[sx][sy]={0,0};//只要标记一下,只要不是-1,都可以
    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;
            q[++tt]={a,b};
            pre[a][b]=t;
        }
    }
    
}

int main()
{
    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);//倒着搜,直接输出路径
    
    PII end(0,0);//初始化一个pair,两个数分别是(0,0)
    
    while(true)
    {
        cout<<end.x<<" "<<end.y<<endl;
        if(end.x==n-1&&end.y==n-1)break;
        end=pre[end.x][end.y];//倒序遍历,每个点存他的前一个点是什么,(0,0)的前一个点是(1,0)
    }
    return 0;
}
//因为宽搜一定是最短路径,所以不需要记录距离
//可以把PII pre[N][N]看作一个二维数组,其中每个元素都是一个pair<int, int>类型的值
//PII pre[N][N] 是一个二维数组,用来记录每个位置的前驱位置。每个位置 (i, j) 上的前驱位置可以用一个二元组 (x, y) 表示,
//即 pre[i][j] 的值是一个 (x, y) 对,表示从 (i, j) 位置到达 (x, y) 位置。这里的 (x, y) 对是在 BFS 遍历中由当前位置 (i, j) 记录下来的。

//在 BFS 遍历的过程中,当我们从队列中取出当前位置 (a, b) 并且发现其邻居 (x, y) 是可以访问的,
//我们就将 (x, y) 的前驱位置设置为 (a, b)。这样,在整个 BFS 遍历结束后,pre[i][j] 中存储的值就是位置 (i, j) 的前驱位置。

//这样做的好处是,当我们需要输出从终点 (n-1, n-1) 到起点 (0, 0) 的路径时,只需要从终点开始,根据每个位置的前驱位置一步步地回溯,
//直到回溯到起点 (0, 0),就可以得到整条路径了。

AcWing 188. 武士风度的牛

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 155,M=N*N;

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

int n,m;
char g[N][N];
PII q[M];
int dist[N][N];//实现距离和走没走过这两个功能

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};
    
    memset(dist, -1, sizeof dist);
    dist[sx][sy]=0;
    
    while(hh<=tt)
    {
        auto 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(dist[a][b]!=-1)continue;
            if(g[a][b]=='H')return dist[t.x][t.y]+1;
            
            dist[a][b]=dist[t.x][t.y]+1;
            q[++tt]={a,b};
        }
    }
}

int main()
{
    cin>>m>>n;
    for(int i=0;i<n;i++)cin>>g[i];
    
    cout<<bfs()<<endl;
    return 0;
}

AcWing 1100. 抓住那头牛

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

using namespace std;

const int N = 1e5+10;

int n,k;
int q[N];//数组模拟队列
int dist[N];//距离数组

int bfs()
{
    memset(dist,-1,sizeof dist);
    dist[n]=0;//把n加到队列中
    q[0]=n;//起点是n,把n加到队列里
    
    int hh=0,tt=0;
    while(hh<=tt)
    {
        int t=q[hh++];//取出队头
        
        if(t==k)return dist[k];
        
        if(t+1<N&&dist[t+1]==-1)//加1再范围内,并且之前没走过
        {
            dist[t+1]=dist[t]+1;//次数加1
            q[++tt]=t+1;//向队尾插入一个数
        }
        
        if(t-1>=0&&dist[t-1]==-1)
        {
            dist[t-1]=dist[t]+1;
            q[++tt]=t-1;
        }
        
        if(t*2<N&&dist[t*2]==-1)
        {
            dist[t*2]=dist[t]+1;
            q[++tt]=t*2;
        }
    }
    return -1;
}


int main()
{
    cin>>n>>k;
    cout<<bfs()<<endl;
    return 0;
}



//有可能会跳过这头牛,再往回走

多源BFS

AcWing 173. 矩阵距离

求每个位置的行和列到1的距离,取一个最短距离
距离指的是曼哈顿距离:行和列的距离之和
在这里插入图片描述
把0改成距离1的距离,把1改成0,因为1到1的距离是0
在这里插入图片描述
有多个起点,分别以每个1作为起点,遍历整个矩阵,保留最小值
做法:队列的第一层存所有1的位置,也就是距离为0的点

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010,M=N*N;

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

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};
          }
    while(hh<=tt)
    {
        auto 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()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>g[i];
    bfs();
    for(int i=0;i<n;i++)
    {
       for(int j=0;j<m;j++)
       cout<<dist[i][j]<<" ";
       cout<<endl;
    }
    return 0;
}

最小步数模型

AcWing 1107. 魔板

思路:
用map或者unordered map做哈希
把12345678放到队列的队头,用宽搜去搜我们的所有状态,直到我们搜到终点为止,
每次扩展的时候,我们分别做A,B,C这三种操作,做完之后得到一个字符串,判断这个字符串之前有没有搜到过,如果没有搜到过,把他的距离更新一下,然后存到我们的队列中,这样就能得到最短距离
记录方案:记录每一个状态是由哪个状态转移过来的,这样就能从终点倒推到起点
需要按照最小字典序输出:我们在扩展每个状态的时候,按照A,B,C的顺序来扩展,就一定能得到最小字典序

#include <iostream>
#include <cstring>
#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;
//存每个状态他是从哪个状态转移过来的,同时存下来操作是A还是B还是C,所以用pair
queue<string>q;//这是队列

void set(string state)//state是一个顺指针的字符串,放回2*4的矩阵中
{
    for(int i=0;i<4;i++)g[0][i]=state[i];
    for(int i=3,j=4;i>=0;i--,j++)g[1][i]=state[j];
}

string get()//把2*4的矩阵变成一个顺时针的字符串
{
    string res;
    for(int i=0;i<4;i++)res+=g[0][i];
    for(int i=3;i>=0;i--)res+=g[1][i];
    return res;
}

string move0(string state)
{
    set(state);
    for(int i=0;i<4;i++)swap(g[0][i],g[1][i]);
    return get();
}


string move1(string state)
{
    set(state);
    char v0=g[0][3],v1=g[1][3];
    for(int i=3;i>0;i--)
       for(int j=0;j<2;j++)
          g[j][i]=g[j][i-1];
          
    g[0][0]=v0,g[1][0]=v1;
    return get();
}

string move2(string state)
{
    set(state);
    int v = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = v;
    return get();
}

//一个是起点,一个是终点
void bfs(string start,string end)
{
    if(start==end)return;//如果起点已经等于终点,就return
    
    q.push(start);//先把起点放进去
    dist[start]=0;//距离是0
    
    while(q.size())//队列不空
    {
        auto t=q.front();//每次取出队头元素
        q.pop();//删掉队头元素
    
        
        string m[3];//进行三次扩展
        m[0]=move0(t);
        m[1]=move1(t);
        m[2]=move2(t);
        
        // for(int i=0;i<3;i++)cout<<"->"<<m[i]<<endl;//调试
        // break;
        
        for(int i=0;i<3;i++)//遍历三个状态
        {
            string str=m[i];
            if(dist.count(str)==0)//如果没有被遍历过
            {
                dist[str]=dist[t]+1;
                pre[str]={char(i+'A'),t};//记录操作和从哪个状态转移过来
                if(str==end)break;
                q.push(str);//入队
            }
        }
        
    }
}
int main()
{
    int x;
    string start,end;
    for (int i = 0; i < 8; i ++ )//初始化end
    {
        cin>>x;
        end+=char(x+'0');//数字i变成字符i
    }
    for(int i=0;i<8;i++)//初始化end       
       start+=char(i+'1');
       
    bfs(start,end);
    
    cout<<dist[end]<<endl;
    
    string res;//表示最终的操作序列
    while(end!=start)//从最终状态开始做,当我们的最终状态不等于初始状态的时候
    {
        res+=pre[end].first;//操作的编号
        end=pre[end].second;//再把end变成一个他的前驱状态
    }
    reverse(res.begin(),res.end());//倒着求的,最后需要翻转一遍
    if(res.size())cout<<res<<endl;
    return 0;
}
//写个整体框架,骨架

别人的题解
bfs 爆搜

从起始字符串开始进行 bfs,直到找到目标字符串位子。

为了得到变换顺序,在 bfs 过程中,保存各个字符串的前序序列和变化方式(由哪个字符进行什么变换,可以得到当前字符串)

具体的:

使用字典 pre 保存变换,key 是当前字符串,value 是个一个 pair, 两个值:由哪个字符串进行了什么变换得到了当前字符。

使用队列 q 用来做 bfs 的状态队列

使用字典 dist 保存变换次数,key 是当前字符串,value 是起始字符串最少经过多少次变换可以得到当前字符串

结束条件是:搜索到了目标字符串

dist[目标字符串] 就是变换次数

根据 pre 反推出变换序列

#include <cstring> // 引入cstring头文件,用于字符串操作
#include <iostream> // 引入iostream头文件,用于输入输出流操作
#include <algorithm> // 引入algorithm头文件,用于算法操作
#include <unordered_map> // 引入unordered_map头文件,用于哈希映射操作
#include <queue> // 引入queue头文件,用于队列操作

using namespace std;

char g[2][4]; // 定义一个二维字符数组g,表示状态
unordered_map<string, pair<char, string>> pre; // 定义一个哈希映射pre,存储状态的前驱和移动方式
unordered_map<string, int> dist; // 定义一个哈希映射dist,存储状态的最短距离

void set(string state)
{
    for (int i = 0; i < 4; i ++ ) g[0][i] = state[i]; // 将状态的前四个字符赋值给g的第一行
    for (int i = 7, j = 0; j < 4; i --, j ++ ) g[1][j] = state[i]; // 将状态的后四个字符赋值给g的第二行
}

string get()
{
    string res;
    for (int i = 0; i < 4; i ++ ) res += g[0][i]; // 将g的第一行字符拼接成字符串
    for (int i = 3; i >= 0; i -- ) res += g[1][i]; // 将g的第二行字符逆序拼接成字符串
    return res;
}

string move0(string state)
{
    set(state);
    for (int i = 0; i < 4; i ++ ) swap(g[0][i], g[1][i]); // 交换g的第一行和第二行的字符
    return get();
}

string move1(string state)
{
    set(state);
    int v0 = g[0][3], v1 = g[1][3]; // 保存g的第一行和第二行的最后一个字符
    for (int i = 3; i > 0; i -- )
    {
        g[0][i] = g[0][i - 1]; // 将g的第一行字符向右移动一位
        g[1][i] = g[1][i - 1]; // 将g的第二行字符向右移动一位
    }
    g[0][0] = v0, g[1][0] = v1; // 将保存的字符放到g的第一行和第二行的第一个位置
    return get();
}

string move2(string state)
{
    set(state);
    int v = g[0][1]; // 保存g的第一行的第二个字符
    g[0][1] = g[1][1]; // 将g的第二行的第二个字符赋值给g的第一行的第二个字符
    g[1][1] = g[1][2]; // 将g的第二行的第三个字符赋值给g的第二行的第二个字符
    g[1][2] = g[0][2]; // 将g的第一行的第三个字符赋值给g的第二行的第三个字符
    g[0][2] = v; // 将保存的字符放到g的第一行的第三个位置
    return get();
}

int bfs(string start, string end)
{
    if (start == end) return 0; // 如果起始状态和目标状态相同,返回0

    queue<string> q; // 定义一个队列q,用于广度优先搜索
    q.push(start); // 将起始状态加入队列
    dist[start] = 0; // 起始状态的最短距离为0

    while (!q.empty())
    {
        auto t = q.front(); // 取出队列的第一个元素
        q.pop(); // 弹出队列的第一个元素

        string m[3]; // 定义一个字符串数组m,存储移动后的状态
        m[0] = move0(t); // 将t进行move0操作得到移动后的状态m[0]
        m[1] = move1(t); // 将t进行move1操作得到移动后的状态m[1]
        m[2] = move2(t); // 将t进行move2操作得到移动后的状态m[2]

        for (int i = 0; i < 3; i ++ )
            if (!dist.count(m[i])) // 如果状态m[i]不在dist中
            {
                dist[m[i]] = dist[t] + 1; // 更新状态m[i]的最短距离
                pre[m[i]] = {'A' + i, t}; // 更新状态m[i]的前驱和移动方式
                q.push(m[i]); // 将状态m[i]加入队列
                if (m[i] == end) return dist[end]; // 如果状态m[i]等于目标状态,返回最短距离
            }
    }

    return -1; // 如果无法到达目标状态,返回-1
}

int main()
{
    int x;
    string start, end;
    for (int i = 0; i < 8; i ++ )
    {
        cin >> x; // 输入一个整数x
        end += char(x + '0'); // 将x转换为字符并添加到end字符串末尾
    }

    for (int i = 1; i <= 8; i ++ ) start += char('0' + i); // 初始化start字符串为"12345678"

    int step = bfs(start, end); // 进行广度优先搜索,得到最短步数

    cout << step << endl; // 输出最短步数

    // 有pre反推出变换序列
    string res;
    while (end != start)
    {
        res += pre[end].first; // 将当前状态的移动方式添加到res字符串末尾
        end = pre[end].second; // 更新当前状态为前驱状态
    }

    reverse(res.begin(), res.end()); // 将res字符串逆序

    if (step > 0) cout << res << endl; // 如果存在最短路径,输出移动方式

    return 0;
}

双端队列广搜

AcWing 175. 电路维修

双向广搜

AcWing 190. 字串变换

A*

AcWing 178. 第K短路

AcWing 179. 八数码

DFS之连通性模型

AcWing 1112. 迷宫 1112

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

using namespace std;

const int N = 110;

int n;
char g[N][N];
bool st[N][N];
int xa,ya,xb,yb;

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;
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=0;i<n;i++)cin>>g[i];
        memset(st,0,sizeof st);
        
        cin>>xa>>ya>>xb>>yb;
        
        if(dfs(xa,ya))cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

AcWing 1113. 红与黑 1113

dfs实现Flood Fill算法
深搜有两种模型
第一种模型是人体内部从某个地方到另外一个地方,为了保证每个点只被搜索一次,所以不能恢复现场(相当于是棋盘内部)
第二种是把人当成一个整体,人和人之间可以传递信息,问我信息能不能传到另外一个人那里去,需要恢复现场(相当于是从一个棋盘变为另外一个棋盘)

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

using namespace std;

const int N = 25;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

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

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++)cin>>g[i];
        
        int x,y;
        for(int i=0;i<n;i++)
           for (int j = 0; j < n; j ++ )
              if(g[i][j]=='@')
              {
                  x=i;
                  y=j;
              }
        memset(st, 0, sizeof st);      
        cout<<dfs(x,y)<<endl;
    }
    return 0;
}

DFS之搜索顺序

AcWing 1116. 马走日 1116

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

using namespace std;

const int N = 25;

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

int n,m;
bool st[N][N];
int ans=0;

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;
    cin>>T;
    while(T--)
    {
        int x,y;
        cin>>n>>m>>x>>y;
        memset(st, 0, sizeof st);
        ans=0;
        dfs(x,y,1);//0表示当前在搜第几个点
        cout<<ans<<endl;
    }
    
    return 0;
}

AcWing 1117. 单词接龙 1117

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

using namespace std;

const int N = 21;

int n;
string word[N];
int g[N][N];//存的是两个单词重叠部分长度的最小值是多少
int used[N];//存每个单词当前用了几次
int ans; //最大长度

void dfs(string dragon,int last)//龙和当前接的最后一个单词的编号
{
    ans=max((int)dragon.size(),ans);
    
    used[last]++;
    
    for(int i=0;i<n;i++)//枚举下一个单词可以填哪个
       if(g[last][i]&&used[i]<2)//如果说last后面可以接i这个单词,使用次数小于2
          dfs(dragon+word[i].substr(g[last][i]),i);//从重合部分往后开始加
    
    used[last]--;
}


int main()
{
    cin>>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],b=word[j];//a表示第一个单词,b表示第二个单词
           //判断a和b的最大公共长度,如果说a在前,b在后,龙要更长,重合部分越短越好
           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);//从word[i]开始搜,当前最后一个单词是i,为了方便判断下一个单词能不能接
    
    cout<<ans<<endl;
    return 0;
}

AcWing 1118. 分成互质组 1118

DFS之剪枝与优化

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/29c0c3c6c96a46f2adfe89846b75d8ed.png#pic_center

AcWing 165. 小猫爬山

在这里插入图片描述

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

using namespace std;

const int N = 20;

int n,m;
int w[N];//猫的重量
int sum[N];//每辆车当前的总和是多少
int ans=N;

void dfs(int u,int k)
{
    //最优性剪枝
    if(k>=ans)return;
    if(u==n)
    {
        ans=k;
        return;
    }
    
    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];//恢复现场
       }
    
    //新开一辆车 
    sum[k]=w[u];//sum是0到k-1,因此sum[k]就是第k+1辆车
    dfs(u+1,k+1);
    sum[k]=0;//恢复现场
    
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>w[i];
    
    //优化搜索顺序
    sort(w,w+n);
    reverse(w,w+n);
    
    dfs(0,0);//当前搜到第0只猫 当前车的数量是0
    cout<<ans<<endl;
    return 0;
}

//从前往后依次枚举每只小猫,每次枚举当前这只小猫应该放到哪辆车上
//用u从0到n-1枚举所有小猫
//1根据重量排序,从大到小搜
//3重量超过W就剪枝
//4新开的车的数量>=ans,剪枝

AcWing 166. 数独

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

using namespace std;

const int N = 9,M=1<<N;

int ones[M];//快速求出一个状态里面有多少个1,打表
int map[M];//快速求出来2的多少次方,log一下是多少,lowbit返回的是2的多少次方,我们要求的是指数是多少,打表
int row[N],col[N],cell[3][3];
char str[100];//棋盘

void init()
{
    for (int i = 0; i < N; i ++ )
        row[i] = col[i] = (1 << N) - 1;//最开始什么都没填,状态应该是9个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)//在(x,y)上填上数字t),is_set是当前在(x,y)填上一个数还是删掉
{
    if (is_set) str[x * N + y] = '1' + t;//填,t是0到8
    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 lowbit(int x)
{
    return x & -x;
}

int get(int x, int y)//求(x,y)这个点能填哪些数
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt)
{
    if (!cnt) return true;//如果没有空格,说明找到了合法方案

    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;
                }
            }

    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);//如果失败就清空这个值
    }

    return false;
}


int main()
{
    for (int i = 0; i < N; i ++ ) map[1 << i] = i;//log以2为底的一个数等于多少
    for (int i = 0; i < 1 << N; i ++ )
        for (int j = 0; j < N; j ++ )
            ones[i] += i >> j & 1;//预处理每个数的二进制表示里面有多少个1

    while (cin >> str, str[0] != 'e')
    {
        init();//预处理行列九宫格

        int cnt = 0;//cnt表示有多少个空位
        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);

        puts(str);
    }

    return 0;
}


//选择格子,看行和列和小格子里哪些数没选过,枚举这个格子可以填哪些数字
//1优化搜索顺序:选择分支最少de格子
//2可行性剪枝:当前枚举的数字不能与行,列,九宫格重复
//位运算优化:可以用一个九位的二进制数表示一行的状态,0表示已经用过,1表示没有用过
//      123456789
//行    010011100
//列
//九宫格
//求行列九宫格的交集,哪个位置上这三个变量都是1
//求三个二进制数的交集可以用&(与运算)
//优化3:lowbit可以在O(1)的时间复杂度之内返回当前二进制数的最后一位1,一个数里面有多少个1,就循环多少次,不用循环9次

AcWing 167. 木棒

AcWing 168. 生日蛋糕

迭代加深

在这里插入图片描述

AcWing 170. 加成序列

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

using namespace std;

const int N = 110;

int n;
int path[N];//答案存在path中

//当前层数,最大层数
bool dfs(int u,int depth)
{
    if(u>depth)return false;
    if(path[u-1]==n)return true;//找到一个合法解
    
    //枚举所有分支
    bool st[N]={0};//排除等效冗余
    for(int i=u-1;i>=0;i--)//从大到小枚举
       for(int j=i;j>=0;j--)//枚举一个组合数就可以,选1,2和2,1是一样的
       {
           int s=path[i]+path[j];//当前的和
           if(s>n||s<=path[u-1]||st[s])continue;//如果总和大于n,总和小于等于最后一个元素,总和已经被搜索过了,就continue
           
           st[s]=true;//标记这个总和被搜索过了
           path[u]=s;//把总和存下来
           if(dfs(u+1,depth))return true;//搜到答案返回true
       }
    
    return false;
}


int main()
{
    path[0]=1;//第一个数是1
    
    while(cin>>n,n)
    {
        int depth=1;
        while(!dfs(1,depth))depth++;
        for(int i=0;i<depth;i++)cout<<path[i]<<' ';
        cout<<endl;
    }
    return 0;
}
//迭代加深:层数从1开始搜,没有答案就扩大范围
//某些分支的层数很深,但是答案很浅
// 1 2 4 8 16 32 64 128
//剪枝1优化搜索顺序:优先枚举较大的数,从大到小枚举
//剪枝2:排除等效冗余:1+4=2+3只需要枚举一种就可以,开个bool数组,判断每个数是否被枚举过,枚举过就不用再枚举了

双向DFS

AcWing 171. 送礼物

IDA*

就是迭代加深加了一个启发式剪枝

AcWing 180. 排书

AcWing 181. 回转游戏

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: acwing算法基础是一门针对算法学习的在线程,在这门程中,学生可以系统地学习和掌握算法基础知识,提高编程水平。为了方便学生学习,acwing提供了网盘服务。 acwing算法基础网盘是一个用于存储程资源的平台。通过这个网盘,学生可以下载程讲义、代码模板以及补充材料等。这些资源都经过精心整理,供学生们参考和学习。 网盘中的资源是按照程章节进行分类的,学生可以根据自己的学习需要,选择性地下载所需的资料。同时,网盘还提供了搜索功能,方便学生快速定位和获取所需资料。 acwing算法基础网盘的使用对于学生们的学习非常有帮助。通过下载和学习这些资源,学生们可以更好地理解程内容,加深对算法的理解。此外,学生们还可以通过研究代码模板,学习优秀的编程思想和技巧,提高自己的编程能力。 总之,acwing算法基础网盘是一项非常便利和实用的服务,为学生们提供了更加全面和深入的学习资源,帮助他们更好地掌握和运用算法知识。 ### 回答2: acwing算法基础是一门优质的算法学习资源,其中的程内容丰富多样,涵盖了算法基础知识、数据结构、动态规划、图论等等。很多学习者都认为这门程对他们的算法学习有很大的帮助。 网盘是指以网络为媒介,提供文件存储和下载服务的云存储平台。acwing算法基础也提供了网盘服务,方便学习者下载程资料并进行学习。 通过acwing算法基础网盘,学习者可以方便地获取到程的各种学习资料,包括讲义、习题集、代码示例等。这些资料可以帮助学习者更好地理解和掌握程的内容。此外,网盘还提供了上传和分享功能,学习者可以将自己的学习心得、代码等资料分享给其他学习者,促进学习者之间的互相学习和交流。 acwing算法基础网盘的优点不仅仅是方便快捷的下载和分享功能,还包括安全可靠的存储环境。学习者可以放心地将自己的学习资料上传到网盘进行备份,减少数据丢失的风险。同时,网盘还提供了多种存储空间容量的选择,满足学习者不同的需求。 总的来说,acwing算法基础网盘为学习者提供了方便、安全和多样化的学习资源下载和分享服务,为学习者的算法学习和进步提供了有力的支持。如果你对算法感兴趣,我推荐你去尝试一下这门精彩的程!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值