第二章 搜索
包括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*
就是迭代加深加了一个启发式剪枝