搜索算法(DFS,BFS等)

搜索算法及练习

1. 搜索算法概述

​ 搜索是一种通过穷举所有可能解的状态,来求得题目所要求的解或者最优解的方法,即通过枚举解的所有可能状态,来寻求一个解或者最优解。在这个过程中,必须确保不会重复搜索已经搜索过的状态,否则会导致循环的产生。同时也要考虑总的状态数是否在可接受的范围内,否则会导致超时或者算法无法停止。

​ 然而有些时候,看起来因为状态数太多而无法进行搜索的题目也可以通过各种剪枝的方法求出解。所谓剪枝,就是在搜索过程中,有意地避开那些虽然也属于可达状态,但是绝对不会是所求解的情况,通过这种方式减少总的所需搜索的状态数,来达到在时限要求内求出解的目的。

​ 想要用搜索算法解题,首先,需要表达出题目的状态空间,即题目所描述的初始状态、可达状态和终结状态,并确定它们之间的转移条件。其次,需要提出一个合理的搜索方式,这个搜索方式必须保证可以到达所有可能的情况,并且不会导致死循环的出现。最后,需要估计这样做是否能在题目所给定的时间和空间限定内解出答案,如果超过了时间和空间限定,是否可以通过剪枝或者改变搜索方式等方法来优化算法,以达到符合限制条件的目的。

​ 在搜索的时候,需要先建立一种搜索的顺序,来保证每一个状态都被搜索到且仅搜索到一次。常用的搜索顺序有两种:宽度搜索深度优先搜索

2. 宽度优先搜索(Breadth First Search,BFS)

概述

​ 宽度优先搜索遍历类似于树的按层次遍历的过程。假设从图中某一点1出发,发现1可以到达2,此时记录下2这个顶点,但是并不从2继续搜索,而是依然寻找1可以到达的点3、4、5…直到所有1可以到达的点都被记录下来,再寻找最早被记录的点,即2,从它开始重新按照1的方式进行搜索,但是对于已经搜索到的点(如1,3)则不再记录,只记录只有2可以到达的之前没有到达过的点即可。

​ 对于《ACM\ICPC算法基础训练教程》P100页中的无向图G进行宽度优先搜索遍历,首先访问1和1的邻接点2和3,然后依次访问2的邻接点4和5以及3的邻接点6和7,最后访问4的邻接点8.由于这些顶点的邻接点均已被访问,并图的所有顶点都被访问,由此完成了图的遍历。得到的顶点访问序列为:
1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 1\rightarrow2\rightarrow3\rightarrow4\rightarrow5\rightarrow6\rightarrow7\rightarrow8 12345678
​ 在搜索遍历的过程中,需要使用数组visit[maxn]记录某个点是否在之前已经到达,同时,为了按照记录的顺序访问点,需要使用队列来记录所有第一次到达的点。

​ 从图的某一顶点v出发,递归地进行宽度优先遍历的伪代码:

void BFS(){
    for(v=0;v<maxn;v++)
        visit[v]=false;
    q.clear();
    q.push(start);	/* 放入队头 */
    while(!q.empty()){
        u=q.front();
        q.pop();	/* 队头元素出队并置为u */
        visited[u]=true;
        visit(u);
        if(!visited[w]){
            q.push(w);	/* 对于每个和u相邻的元素w的尚未访问的邻接结点w入队列q */
        }
    }
}

通过观察宽度优先搜索可以发现,因为visit数组的存在,每一个点最多只会进入队列一次,因此,遍历图的过程实质上是对每个顶点查找其相邻点的过程。其时间复杂度则取决于对应的存储结构。若使用邻接矩阵,那么算法所需时间复杂度为 O ( n 2 ) O(n^2) O(n2),而当以邻接表作为图的存储结构时,所需时间为 O ( n + m ) O(n+m) O(n+m)

2.1 Robot in Maze

题目描述:有一个机器人被困在一个迷宫中,请你告诉它如何到达目的地。

​ 迷宫是一个 M × N M\times N M×N的矩阵,有一些是空的,还有一些被墙填满。机器人不能移动到被墙填满的格子里,也不能移动到矩阵外。机器人只能接受三个操作:向左转、向右转和前进。机器人开始的时候面朝北。输入一个矩阵,用#代表墙,代表空,S代表机器人的起始点,T代表机器人的目的地。输出让机器人到达目的地的最小操作数。

​ 输入的第一行表示样例数,下面的一行有两个数,分别表示迷宫的 M M M N N N,下面的 M M M行每行有 N N N个符号,表示整个迷宫。 1 ≤ M , N ≤ 100 1\leq M,N \leq 100 1M,N100

​ 输出只有一行,即最小操作数。

输入样例

2
5 5
#####
#...#
#.#.#
#S#T#
#####
4 5
#.#.#
#.#.#
#S#T#
#####

输出样例

8
-1

题目解析

​ 题目要找最少的操作数,可以用BFS遍历的方法进行搜索,即从起点开始进行BFS,对每一个新到达的位置记录当前到达的时间,这样当第一次到达终点就是最早到达的时候。机器的某一时刻的状态应该包括地点和朝向,此时用
v i s i t e d [ 100 ] [ 100 ] [ 4 ] visited[100][100][4] visited[100][100][4]
多出的维度用来表示机器人所在的方向。

#include <cstdio>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

char map[200][200];         /* 读入迷宫所需数组 */
int visited[200][200][4];   /* 表示该位置是否被访问 */
int row,col;                /* 行,列 */
int sx[4]={-1,0,1,0};       /* 行走方向辅助数组 */
int sy[4]={0,1,0,-1};

typedef struct Node{
    int x;
    int y;
    int face;
    int step;
}node;                      /* 机器人所走过的每个结点,记录坐标、朝向以及当前步数 */
node s,e;                   /* 起点终点 */
node queue[160000];         /* BFS所需要的队列 */

int judge(int x,int y){     /* 判断是否可以到达 */
    return x>=0&&x<row&&y>=0&&y<col&&map[x][y]!='#';
}

int BFS(){
    int i,j,k,top,tx,ty,ex,ey,face;
    ex=e.x,ey=e.y;
    queue[0]=s;             /* 将起点加入队列 */
    visited[s.x][s.y][0]=1;
    top=1;
    for(int i=0;i<top;i++){/* 开始搜索 */
        /* 向前进 */
        tx=queue[i].x+sx[queue[i].face];/* 使用辅助数组 */
        ty=queue[i].y+sy[queue[i].face];
        face=queue[i].face;
        if(judge(tx,ty)&&!visited[tx][ty][queue[i].face]){
            queue[top].x=tx;
            queue[top].y=ty;
            queue[top].face=queue[i].face;
            queue[top].step=queue[i].step+1;
            visited[tx][ty][face]=1;
            if(map[tx][ty]=='T'){   /* 判断是否目标 */
                return queue[top].step;
            }
            top++;
        }
        /* 向右转 */
        tx=queue[i].x;
        ty=queue[i].y;
        face=(queue[i].face+1)%4;
        if(!visited[tx][ty][face]){
            queue[top].x=tx;
            queue[top].y=ty;
            queue[top].face=face;
            queue[top].step=queue[i].step+1;
            visited[tx][ty][face]=1;
            if(map[tx][ty]=='T'){   /* 判断是否目标 */
                return queue[top].step;
            }
            top++;
        }
        /* 向左转 */
        tx=queue[i].x;
        ty=queue[i].y;
        face=(queue[i].face+3)%4;
        if(!visited[tx][ty][face]){
            queue[top].x=tx;
            queue[top].y=ty;
            queue[top].face=face;
            queue[top].step=queue[i].step+1;
            visited[tx][ty][face]=1;
            if(map[tx][ty]=='T'){   /* 判断是否目标 */
                return queue[top].step;
            }
            top++;
        }
    }
    return -1;                      /* 既无法到达目标 */
}
int main(){
    int n,i,j,k;
    char c;
    scanf("%d",&n);
    while(n--){
        scanf("%d %d",&row,&col);   /* 读入行列 */
        getchar();
        for(i=0;i<row;i++){
            for(j=0;j<col;j++){
                c=getchar();
                map[i][j]=c;
                if(map[i][j]=='S'){ /* 记录开始位置 */
                    s.x=i;
                    s.y=j;
                }else if(map[i][j]=='T'){/* 结束位置 */
                    e.x=i;
                    e.y=j;
                }
            }
            getchar();
        }
        s.face=0;
        s.step=0;
        memset(visited,0,sizeof(visited));
        printf("%d\n",BFS());
    }
    return 0;
}

2.2 Yet Another Multiple Problem

题目描述

给一个数 n ( 1 < n < 10000 ) n(1<n<10000) n(1<n<10000),求 n n n的最小的倍数 x x x,使得 x x x不包含 m m m个特定的数字。输入的第一个数字表示 n n n,第二个数表示 m m m。接下来的一行有 m m m个数字,表示不包含的数字。

输出为当前第几组数据以及最小倍数 x x x,若 x x x没有解,则输出-1。

输入样例

2345 3
7 8 9
100 1
0

输出样例

Case1: 2345
Case2: -1

完整程序

#include <cstring>
#include <cstdio>
#include <queue>
#include <string>
#include <algorithm>
#define N 10005
using namespace std;
bool vis[N],del[10];    /* 是否已经访问的记录数组,以及禁忌数字记录数组 */
int n,pre[N];           /* 记录前置数字来输出 */
char text[N];           /* 为输出准备的数组 */
bool bfs(){
    queue<int> q;
    q.push(0);
    int cur;
    while(!q.empty()){
        cur=q.front();
        q.pop();
        for(int i=0;i<10;i++){
            if(del[i]==1||cur==0&&i==0){
                /* 如果是不允许使用的数字或者当前余数和数字都为0,继续 */
                continue;
            }
            int yu=(cur*10+i)%n;    /* 计算新的余数 */
            if(vis[yu]){            /* 已经到达过,不保存 */
                continue;
            }
            text[yu]='0'+i;
            vis[yu]=true;
            pre[yu]=cur;            /* 记录到到达这个余数前面那个数 */
            q.push(yu);
            if(yu==0){
                return true;
            }
        }
    }
    return false;
}
void print(){
    string ans;
    int p=0;
    while(p!=0||ans.empty()){
        ans+=text[p];
        p=pre[p];
    }
    reverse(ans.begin(),ans.end());
    puts(ans.c_str());
}
int main(){
    int m,cas=0;
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(vis,0,sizeof(vis));
        memset(del,0,sizeof(del));
        while(m--){
            int k;
            scanf("%d",&k);
            del[k]=1;
        }
        printf("Case%d: ",++cas);
        if(!bfs()){
            printf("-1\n");
        }else{
            print();
        }
    }
    return 0;
}

2.3 马的遍历

传送门

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int n,m,x,y;
int ans[401][401];      /* 记录步数 */
bool visited[401][401]; /* 记录是否访问 */
const int dx[8]={-1,-2,-2,-1,1,2,2,1};
const int dy[8]={2,1,-1,-2,2,1,-1,-2};
queue<pair<int,int>> q; /* 不含有排序功能 */
int main(){
    memset(ans,-1,sizeof(ans));
    memset(visited,false,sizeof(visited));
    cin>>n>>m>>x>>y;
    ans[x][y]=0;visited[x][y]=true;
    q.push(make_pair(x,y));
    while(!q.empty()){
        int xx=q.front().first,yy=q.front().second;q.pop();
        for(int i=0;i<8;i++){
            int u=xx+dx[i],v=yy+dy[i];
            if(u<1||u>n||v<1||v>m||visited[u][v])
                continue;
            visited[u][v]=true;q.push(make_pair(u,v));ans[u][v]=ans[xx][yy]+1;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            printf("%-5d",ans[i][j]);
        }
        printf("\n");
    }
    return 0;
}

2.4 Fire!

传送门

#include<bits/stdc++.h>
using namespace std;
struct Struct
{
	int X;//当前横坐标
	int Y;//当前纵坐标
	int Step;//当前时间
};
Struct Xy[1000001];
int Case;//数据组数
int N,M;//行、列
int Dx,Dy;//下一个坐标
bool GetAns;//记录是否找到解
bool Vis[1001][1001];//记录是否来过某点
char Map[1001][1001];//输入进来的地图
int Fire[1001][1001];//记录火焰到某个点所需要的时间
int Dir[4][2]={1,0,0,1,-1,0,0,-1};//每次往相邻的四个方向拓展
Struct First;//人物初始位置
Struct Nxt;//下一个点
Struct Now;//当前点
int main()
{
	int i,j;
	cin>>Case;
	while(Case--)//多组数据
	{
		queue<Struct>Q;//这就是队列
		cin>>N>>M;
		for(i=1;i<=N;i++)
		{
			for(j=1;j<=M;j++)
			{
				Fire[i][j]=N*M;//刚开始现将时间设的够大,方便取最小值
				Vis[i][j]=false;//刚开始每个点都没去过
			}
		}
		for(i=1;i<=N;i++)
		{
			for(j=1;j<=M;j++)
			{
				cin>>Map[i][j];//输入当前点的地形
				if(Map[i][j]=='F')
				{
					Nxt.X=i;
					Nxt.Y=j;
					Nxt.Step=0;
					Fire[i][j]=0;
					Q.push(Nxt);//如果是火源,加入队列
				}
				if(Map[i][j]=='#')
				{
					Fire[i][j]=0;//如果是墙壁,那么无法到达
				}
				if(Map[i][j]=='J')
				{
					First.X=i;//记录人物初始横坐标
					First.Y=j;//记录人物初始纵坐标
					First.Step=0;
				}
			}
		}
		while(!Q.empty())//第一个BFS
		{
			Now=Q.front();//取出队首
			Q.pop();//弹掉队首
			for(i=0;i<4;i++)//向四周扩散
			{
				Dx=Now.X+Dir[i][0];//下一个点的横坐标
				Dy=Now.Y+Dir[i][1];//下一个点的纵坐标
				if(Now.Step+1<Fire[Dx][Dy])//取较小值
				{
					Fire[Dx][Dy]=Now.Step+1;//更新较小值
					Nxt.X=Dx;
					Nxt.Y=Dy;
					Nxt.Step=Now.Step+1;
					Q.push(Nxt);//继续BFS
				}
			}
		}
		Q.push(First);//将人物初始位置加入队列
		GetAns=false;//先设为未找到答案
		while(!Q.empty())//第二个BFS
		{
			Now=Q.front();//取出队首
			Q.pop();//弹掉队首
			if(Now.X==1||Now.Y==1||Now.X==N||Now.Y==M)//如果到了边界,结束搜索
			{
				GetAns=true;//设为已经找到了解
				cout<<Now.Step+1<<endl;//输出步数,记得+1
				break;//结束搜索
			}
			for(i=0;i<4;i++)//向四周扩散
			{
				Dx=Now.X+Dir[i][0];//下一个点的横坐标
				Dy=Now.Y+Dir[i][1];//下一个点的纵坐标
				if(Now.Step+1<Fire[Dx][Dy]&&!Vis[Dx][Dy])//是否去过,并判断能否进去
				{
					Vis[Dx][Dy]=true;//标记为去过了
					Nxt.X=Dx;
					Nxt.Y=Dy;
					Nxt.Step=Now.Step+1;
					Q.push(Nxt);//加入队列
				}
			}
		}
		if(!GetAns)//如果没有找到答案,输出IMOPOSSIBLE
		{
			cout<<"IMPOSSIBLE"<<endl;
		}
	}
	return 0;
}

3. 深度优先搜索(Depth First Search,DFS)

概述

​ 深度优先搜索一般使用递归来完成,因此代码长度更短小。深度优先搜索遍历类似于树的先根遍历,是树的先根遍历的推广。假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某个顶点 v v v出发,访问此顶点,然后依次从 v v v的未被访问的邻接点出发深度优先遍历此图,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。

​ 深度优先搜索是一个递归的过程。为了判断在遍历过程中每个点是否被访问过,需要设定 v i s i t e d [ m a x n ] visited[maxn] visited[maxn]数组来记录该顶点是否被访问。

伪代码

void DFS(node v){
    /* 从第v个顶点出发递归地深度优先遍历 */
    visited[v]=true;
    if(!visited[w]) DFS(w);		/* 对v的尚未被访问的邻接顶点w递归调用DFS */
}

3.1 八皇后问题

传送门

图示遍历过程

#include <iostream>
using namespace std;
const int N = 1e3;
int n;
int a[100],b[100],c[100],d[100];
/* a数组表示的是行 */
/* b数组表示的是列 */
/* c数组表示的是左下到右上的对角线 */
/* d数组表示的是左上到右下的对角线 */
int total;  /* 记录解的总个数 */
void print1(){
    if(total<=2){
        for(int k=1;k<=n;k++)
            cout<<a[k]<<" ";
        cout<<endl;
    }
    total++;
}
void dfs(int i){
    if(i>n){
        print1();
        return;
    }else{
        for(int j=1;j<=n;j++){
            if((!b[j])&&(!c[i+j])&&(!d[i-j+n])) /* 如果没有皇后占领 */
            {
                a[i]=j;     /* 打标记 */
                b[j]=1;
                c[i+j]=1;
                d[i-j+n]=1;
                dfs(i+1);   /* 继续向下搜索 */
                b[j]=0;     /* 如果行不通则清楚标记 */
                c[i+j]=0;
                d[i-j+n]=0;
            }
        }
    }
}
int main(){
    cin>>n;
    dfs(1);
    cout<<total;
    return 0;
}

3.2 迷宫

传送门

AC代码

#include <cstdio>
int n,m,t;
int sx,sy,ex,ey,l,r;
int ma[6][6];
int visited[6][6];
int dx[4]={0,0,1,-1};
int dy[4]={-1,1,0,0};
int ans=0;
void dfs(int x,int y){
    if(x==ex&&y==ey){
        ans++;
        return;
    }
    else{
        for(int i=0;i<=3;i++){
            if(x+dx[i]>=1&&x+dx[i]<=n&&y+dy[i]>=1&&y+dy[i]<=m&&visited[x+dx[i]][y+dy[i]]==0&&ma[x+dx[i]][y+dy[i]]==1){
                visited[x][y]=1;
                dfs(x+dx[i],y+dy[i]);
                visited[x][y]=0;
            }
        }
    }
}
int main(){
    scanf("%d %d %d",&n,&m,&t);
    for(int ix=1;ix<=n;ix++)
        for(int iy=1;iy<=m;iy++)
            ma[ix][iy]=1;
    scanf("%d %d %d %d",&sx,&sy,&ex,&ey);
    for(int i=1;i<=t;i++){
        scanf("%d %d",&l,&r);
        ma[l][r]=0;
    }
    dfs(sx,sy);
    printf("%d",ans);
    return 0;
}

3.3 奇怪的电梯

传送门

dfs做法

#include <cstdio>
#include <algorithm>
using namespace std;
int n,a,b,ans=0x7ffffff;
int reach[205];
bool visited[205];
void dfs(int now,int sum){
    if(now==b)
        ans=min(ans,sum);
    if(sum>ans) return;
    visited[now]=1;
    if(now+reach[now]<=n&&!visited[now+reach[now]]) dfs(now+reach[now],sum+1);
    if(now-reach[now]>=1&&!visited[now-reach[now]]) dfs(now-reach[now],sum+1);
    visited[now]=0; /* 清除标记 */
}
int main(){
    scanf("%d %d %d",&n,&a,&b);
    for(int i=1;i<=n;i++) scanf("%d",&reach[i]);
    visited[a]=1;
    dfs(a,0);
    if(ans!=0x7ffffff) printf("%d",ans);
    else printf("-1");
    return 0;
}

bfs做法

#include<cstdio>
#include <queue>
using namespace std;
int n,a,b,to[205];
bool vis[205];
struct node{int id,step;}x;
queue<node> q;
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    for(int i=1;i<=n;i++) scanf("%d",&to[i]);
    q.push((node){a,0});
    while(q.size())
    {
        x=q.front();q.pop();
        if(x.id==b) break;
        if(x.id+to[x.id]<=n&&!vis[x.id+to[x.id]])
        {
            q.push((node){x.id+to[x.id],x.step+1});
            vis[x.id+to[x.id]]=1;
        }
        if(x.id-to[x.id]>=1&&!vis[x.id-to[x.id]])
        {
            q.push((node){x.id-to[x.id],x.step+1});
            vis[x.id-to[x.id]]=1;
        }
    }
    if(x.id==b) printf("%d",x.step);
    else printf("-1");
    return 0;
}

3.4 [NOIP2017 提高组] 奶酪

传送门

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAX_N=1010;
struct cir{
    double x,y,z;
    bool operator<(const cir &cpr)const
    {
        return z<cpr.z;
    }
}p[MAX_N];
bool fnd=0;
int n;
double h,r;
bool visited[MAX_N];
double dist(double x1,double y1,double z1,double x2,double y2,double z2){
    return sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1));
}
void dfs(cir now,int num){
    if(now.z+r>=h){
        fnd=1;
        return;
    }
    visited[num]=1;
    for(int i=1;i<=n;i++){
        if(fnd)
            return;
        if(!visited[i]&&dist(now.x,now.y,now.z,p[i].x,p[i].y,p[i].z)<=r*2)
            dfs(p[i],i);
    }
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(visited,0,sizeof(visited));
        memset(p,0,sizeof(p));fnd=0;
        scanf("%d%lf%lf",&n,&h,&r);
        for(int i=1;i<=n;i++)
            scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].z);
        sort(p+1,p+n+1);
        for(int i=1;i<=n;i++)
            if(p[i].z-r<=0)
                dfs(p[i],i);
        if(fnd)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

3.5 [USACO05DEC]Scales S

传送门

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll sum[1005],a[1005],ans,n,c;
inline ll read(){
    char c=getchar();
    int f=1;
    ll x=0;
    while(c<'0'||c>'9'){
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*f;
}
void dfs(int now,long long x)
{
    if(x>c)return;
    if(sum[now-1]+x<=c)
    {
        ans=max(ans,sum[now-1]+x);
        return;
    }
    ans=max(ans,x);
    for(int i=1;i<now;i++)
        dfs(i,x+a[i]);
    return;
}
int main()
{
    n=read();c=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        sum[i]=sum[i-1]+a[i];   /* 前缀和 */
    }
    dfs(n+1,0);
    printf("%lld\n",ans);
    return 0;
}

3.6 油田(连通块)

传送门

4. 剪枝

4.1 小木棍 [数据加强版]

传送门

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    if(f) return x;
    return 0-x;
}
int n,m,a[66],next[66],cnt,sum,len;
bool used[66],ok; //used数组即优化5的vis数组,记录每根木棍是否用过;ok记录是否已找到答案。 
bool cmp(int a,int b){return a>b;}
void dfs(int k,int last,int rest){ //k为正在拼的木棍的编号,last为正在拼的木棍的前一节编号,rest为该木棍还未拼的长度
    int i;
    if(!rest){ //未拼的长度为0,说明这根原始长棍拼完了,准备拼下一个 
        if(k==m){ok=1; return;} //优化6,全部拼完并符合要求,找到答案,直接返回 
        
        for(i=1;i<=cnt;i++) //找一个还没用的最长的木棍打头即可。反正要想全都拼接成功,每根木棍都得用上 
            if(!used[i]) break;
        used[i]=1; 
        dfs(k+1,i,len-a[i]);
        used[i]=0;
        if(ok) return; //优化6,找到答案就退出 
    }
    //优化4,二分找第一个 木棍长度不大于未拼长度rest 的位置 
    int l=last+1, r=cnt, mid;
    while(l<r){
        mid=(l+r)>>1;
        if(a[mid]<=rest) r=mid;
        else l=mid+1;
    }
    for(i=l;i<=cnt;i++){
        if(!used[i]){ //优化5,判断木棍是否用过 
            used[i]=1;
            dfs(k,i,rest-a[i]);
            used[i]=0;
            if(ok) return; //优化6,找到答案就退出 
            
            if(rest==a[i] || rest==len) return; //优化7 
            i=next[i]; //优化3 
            if(i==cnt) return;
        }
    }
    //到了这里,说明这时候拼不成当前这根原始木棍了,传回失败信息并修改之前拼的木棍 
}
int main(){
    n=read();
    int d;
    for(int i=1;i<=n;i++){
        d=read();
        if(d>50) continue;
        a[++cnt]=d;
        sum+=d;
    }
    sort(a+1,a+cnt+1,cmp); //优化1,木棍按长度从大到小排序 
    //优化3,预处理next数组 
    next[cnt]=cnt;
    for(int i=cnt-1;i>0;i--){
        if(a[i]==a[i+1]) next[i]=next[i+1];
        else next[i]=i;
    }
    for(len=a[1];len<=sum/2;len++){ //枚举原始长度 
        if(sum%len!=0) continue; //如果不能拼出整数根 就跳过 
        m=sum/len; //优化6中的那个计算 
        ok=0;
        used[1]=1;
        dfs(1,1,len-a[1]);
        used[1]=0;
        if(ok){printf("%d\n",len); return 0;} //优化6,输出答案,退 
    }
    printf("%d\n",sum); return 0;
}

4.2 [NOIP2004 提高组] 虫食算(数论+搜索)

传送门

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 30
int a[maxn],b[maxn],c[maxn];
int num[maxn],Next[maxn],n,cnt;
char s1[maxn],s2[maxn],s3[maxn];
bool used[maxn];
bool Judge() {
    for(int i=n-1,x=0;i>=0;i--) {
        int A=num[a[i]],B=num[b[i]],C=num[c[i]];
        if((A+B+x)%n!=C) return false;
        x=(A+B+x)/n;
    }
    return true;
}
bool CanPrune() {//prune: 剪枝—百度翻译。
    if(num[a[0]]+num[b[0]]>=n)
        return true;
    for(int i=n-1;i>=0;i--) {
        int A=num[a[i]],B=num[b[i]],C=num[c[i]];
        if(A==-1||B==-1||C==-1) continue;
        if((A+B)%n!=C&&(A+B+1)%n!=C)
            return true;
    }
    return false;
}
void Print() {
    for(int i=0;i<n;i++)
        printf("%d ",num[i]);
    exit(0);
}
void dfs(int x) {
    if(CanPrune()==true) return;
    if(x==n) {
        if(Judge()==true) Print();
        return;
    }
    for(int i=n-1;i>=0;i--)
        if(used[i]==false) {
            num[Next[x]]=i;
            used[i]=true;
            dfs(x+1);
            num[Next[x]]=-1;
            used[i]=false;
        }
    return;
}
inline int id(char c) {
    return c-'A';
}
void GetNext(int x) {
    if(used[x]==false) {
        used[x]=true;
        Next[cnt++]=x;
    }
    return;
}
int main() {
    scanf("%d",&n);
    scanf("%s%s%s",s1,s2,s3);
    for(int i=0;i<n;i++) {
        a[i]=id(s1[i]);
        b[i]=id(s2[i]);
        c[i]=id(s3[i]);
        num[i]=-1;
    }
    for(int i=n-1;i>=0;i--) {
        GetNext(a[i]);
        GetNext(b[i]);
        GetNext(c[i]);
    }
    for(int i=0;i<n;i++) used[i]=false;
    dfs(0);
    return 0;
}

5. A*算法

5.1 八数码难题(哈希+搜索)

传送门

6. 迭代加深搜索

6.1 快速幂计算 Power Calculus

传送门

7. 双向宽度优先搜索

7.1 八数码难题

传送门

8. 舞蹈链

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeGLMath

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值