搜索专题第22组题解(HNEAU)

搜索专题第22组题解

搜索专题
比赛密码:202201110000
还可以去我的博客查看:传送门
写题之前你至少要搞懂深搜,广搜的原理,队列(先进先出)和(后进先出)的特点

深搜用堆栈存储,深搜的路径像面条一样,牺牲时间换空间,适用于求全部解的题等

广搜用队列存储,广搜的路径像水波一样,是往四周蔓延的,牺牲空间换时间,适用于求最短解的题等

A - 棋盘问题

建议先看全排列

#include<bits/stdc++.h>
using namespace std;
int vis[30],n,k,sum,m;
char a[30][30];
void dfs(int p){
	if(k==m){
		sum++;
		return;
	}
	if(p>=n)
	return;
	for(int j=0;j<n;j++){
		if(vis[j]==0&&a[p][j]=='#'){
			vis[j]=1;//依旧先标记后复原
			m++;
			dfs(p+1);
			vis[j]=0;
			m--;
		}
	}
	dfs(p+1);
}
int main()
{
	while(scanf("%d%d",&n,&k)&&n!=-1&&k!=-1){
		sum=0;
		m=0;
		for(int i=0;i<n;i++)
		scanf("%s",&a[i]);
		dfs(0);
		printf("%d\n",sum);
	}
	return 0;
}

B - Perket

#include<bits/stdc++.h>
using namespace std;
int flag[11]={0};
int a[11],b[11];
int c=1,y=0;
int N;
int ans=1e11;
void dfs(int x){
	if(x>=N) 
	return;
	else{
		for(int i=0;i<N;i++){
			if(flag[i]==0){
				c*=a[i];//依旧先标记后复原
				y+=b[i];
				flag[i]=1;
				ans=min(ans,abs(c-y));
				dfs(i+1);
				c/=a[i];
				y-=b[i];
				flag[i]=0;
			}
		}
	}
}
int main(){
	cin>>N;
	for(int i=0;i<N;i++){
		cin>>a[i]>>b[i];
	}
	dfs(0);
	cout<<ans;
	return 0;
}

C - 全排列

这里是字符全排列,不过和数字全排列没什么区别,用深搜遍历出每一种可能就行了,如果想省事,用c++里的next_permutation()函数就行了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char a[25],b[25];//用a数组读入字符串,b数组记下路径
int n,vis[25];//标记数组,搜索过的数就标记好,防止重复
void dfs(int p){
	if(p==n){
		b[n]='\0';
		cout<<b<<endl;//可以这样输出字符串
	}
	for(int i=0;i<n;i++)//如果函数调用到第m层,for循环实际上也在dfs前进入到了第m轮,那剩下的(n-m)轮呢,就是用来产生分支的
		if(!vis[i]){
			vis[i]=1;//dfs前做好标记(记住)
			b[p]=a[i];
			dfs(p+1);
			vis[i]=0;//dfs后删除标记,不同排列有相同数字,走的时候要取消标记(记住)
		}
} 
int main(){
	scanf("%s",a);
	n=strlen(a);
	dfs(0);//习惯上可以从0/1开始,和for循环里i的初值保持一致
	return 0;
}

D - 自然数拆分

#include<bits/stdc++.h>
using namespace std;
int a[41],s,m;
void pr(int s)
{
	cout<<m<<'=';
    for(int i=1;i<=s-2;i++)
	printf("%d+",a[i]);
    printf("%d\n",a[s-1]);
}
void dfs(int n,int s,int pre)//pre确保要减的数是递增的
{
    if(n==0&&s>1){//结束条件:进行至少一轮并且减完为零
        pr(s);
        return;
    }
    for(int i=pre;i<=n;i++){
        a[s]=i;
        dfs(n-i,s+1,i);
    }
}
int main()
{
    cin>>m;
    dfs(m,1,1);
    return 0;
}

E - Prime Ring Problem

#include<bits/stdc++.h>
using namespace std;
int n,a[17],id=0;
bool vis[17];
bool prime(int n){
    for(int i=2;i<= sqrt(n);i++){
        if(n % i == 0) 
		return false;
    }
    return true;
}
void dfs(int x){
    if (x>n&&prime(a[x-1]+a[1])){//要判断第一个数和最后一个数的和是否为素数
        for(int i=1;i<=n;i++){
            cout<<a[i];
            if (i!=n)
			cout<<" ";
        }
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        if(prime(i+a[x-1])&&!vis[i]){
            vis[i]=true;
            a[x]=i;
            dfs(x+1);
            vis[i]=false;
        }
    }
}
int main()
{
	int t=0;
    while(cin>>n){
    	t++;
    	if(t>=2)
    	cout<<endl;
        cout<<"Case "<<++id<<":\n";
        memset(vis,false,sizeof(vis));
        a[1]=1;
        vis[1]=true;
        dfs(2);
    }
    return 0;
}

F - Red and Black

#include<bits/stdc++.h>
using namespace std;
int W,H;
char a[21][21];
int f(int x,int y){
    if (x<0||x>=W||y<0||y>=H)
        return 0;
    if (a[x][y]=='#'){
        return 0;
    }else{
        a[x][y]='#';
        return 1 + f(x-1,y)+f(x+1,y)+f(x,y-1)+f(x,y+1);//求所以可走的路径,将和相加
    }
}
int main()
{
    int i,j,num;
    while(scanf("%d%d",&H,&W)&&W!=0&&H!=0){
        for(i=0;i<W;i++)
            scanf("%s",a[i]);
        for(i=0;i<W;i++)
            for(j=0;j<H;j++)
                if(a[i][j]=='@') 
				printf("%d\n",f(i, j));
    }
    return 0;
}

G - Knight Moves

提醒一点,万能头虽好用,但里面有很多关键字可能会和你的变量名冲突,比如list,visit

这道题应该用广搜,不知道c++队列操作的自行了解一下

#include<bits/stdc++.h>
using namespace std;
const int len=302;
struct horse{
    int x;
    int y;
    int step;
};
bool Visit[len][len];
const int way[8][2]={{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1}};
void bfs(int s1,int e1,int s2,int e2,int lenth){
    queue<horse> q;
    horse start,now,next;
    start.x=s1;
    start.y=e1;
    start.step=0;
    Visit[start.x][start.y]=true;
    q.push(start);
    while(!q.empty()){
        now=q.front();
        q.pop();
        if(now.x==s2&&now.y==e2){
            cout<<now.step<<endl;
            return;
        }
        for(int i=0;i<8;i++){
            next.x=now.x+way[i][0];
            next.y=now.y+way[i][1];
            if(next.x>=0&&next.x<lenth&&next.y>=0&&next.y<lenth&&!Visit[next.x][next.y]){
                next.step=now.step+1;
                q.push(next);
                Visit[next.x][next.y]=true;
            }
        }
    }
}
int main()
{
    int n;
    cin>>n;
    int l;
    int x1,x2,y1,y2;
    for(int i=0;i<n;i++){
        cin>>l;
        cin>>x1>>y1;
        cin>>x2>>y2;
        memset(Visit,false,sizeof(Visit));
        bfs(x1,y1,x2,y2,l);
    }
    return 0;
}

H - Oil Deposits

这个题就是求矩阵中有多少个“分开”的区域,还可以变形为求最大面积等等

#include<bits/stdc++.h>
using namespace std;
char a[1000][1000];
int n,m;
void dfs(int x,int y){
    int i,j,b,c;
    for(i=-1;i<=1;i++){//这个写法爱了,这就是用循环表示8个方向
        for(j=-1;j<=1;j++){
            b=i+x;
            c=y+j;
            if(b>=0&&b<n&&y>=0&&y<m&&a[b][c]=='@'){//如果遇到@就开始搜索并且把@变为*
                a[b][c]='*';//这也算是一种标记
                dfs(b,c);
            }
        }
    }
}
int main()
{
    int i,j,cnt=0;
    while(scanf("%d %d",&n,&m)!=EOF&&n!=0&&m!=0){
        memset(a,'0',sizeof(a));//学会用memset初始化数组,因为是字符数组,全初始化为字符0,防止上一轮残留数据的干扰
        cnt=0;
        for(i=0;i<=n-1;i++)
            scanf("%s",a[i]);
        for(i=0;i<=n-1;i++){
            for(j=0;j<=m-1;j++){
                if(a[i][j]=='@'){
                    cnt++;//遇到油层就要计数
                    dfs(i,j);
                }
            }
        }
        printf("%d\n",cnt);
    }
}

I - Lake Counting

跟上一题一模一样,就是把@换成了w,*换成了.,并且无输入结束标志。

#include<bits/stdc++.h>
using namespace std;
char a[1000][1000];
int n,m;
void dfs(int x,int y){
    int i,j,b,c;
    for(i=-1;i<=1;i++){
        for(j=-1;j<=1;j++){
            b=i+x;
            c=y+j;
            if(b>=0&&b<n&&y>=0&&y<m&&a[b][c]=='W'){
                a[b][c]='.';
                dfs(b,c);
            }
        }
    }
}
int main()
{
    int i,j,cnt=0;
    while(scanf("%d %d",&n,&m)!=EOF){
        memset(a,'0',sizeof(a));
        cnt=0;
        for(i=0;i<=n-1;i++)
            scanf("%s",a[i]);
        for(i=0;i<=n-1;i++){
            for(j=0;j<=m-1;j++){
                if(a[i][j]=='W'){
                    cnt++;
                    dfs(i,j);
                }
            }
        }
        printf("%d\n",cnt);
    }
}

J - 二叉树先序遍历

不知道什么是二叉数和先序遍历的先去查查,已经在计导书里学过了,思路很简单,定义一个包含左右节点的结构体,如果树非空,就做三个操作:

1:输出节点;

2:先遍历左子树;

3:之后遍历右子树,如果为空就返回上一层;

#include<bits/stdc++.h>
using namespace std;
struct p{
	int l;
	int r;
}t[100010];
void dfs(int x){
	if(x==0)
	return;
	cout<<x<<endl;//第1步
	dfs(t[x].l);//第2步
	dfs(t[x].r);//第3步
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>t[i].l>>t[i].r;//依次读入左右节点
	dfs(1);
	return 0;
}

之后在学数据结构的时候还有中序遍历、后序遍历,实现思路差不多

K - 迷宫(一)

学会用数组表示方向,不一定要二维数组,两个一维的也行,甚至可以用循环表示

#include<bits/stdc++.h>
using namespace std;
int n,m;
bool f;
char mp[15][15];
int vis[15][15];
int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};//定义一个二维数组存方向,比如说(x-1,y+0)就是向上一个单位,使用方便
bool in(int x, int y)
{
    return 0<=x&&x<n&&0<=y&&y<m;
}
void dfs(int x, int y)
{
    if (mp[x][y]=='T'){//结束标志
        f=true;
        return;
    }
    if (!in(x,y)||mp[x][y]=='*'||vis[x][y]){
        return;//看看有没有越界,撞墙,走过
    }
    vis[x][y]=1;//dfs前做好标记,不能再走以前的路,所以不用再消除标记
    for (int i = 0; i < 4; i++){
        int tx=x+dir[i][0];//移动坐标
        int ty=y+dir[i][1];
        dfs(tx,ty);
    }
    return;
}
int main()
{
	scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        scanf("%s",mp[i]);
    }
    int x,y;
    for(int i=0;i<n;i++){
       for(int j=0;j<m;j++){
           if(mp[i][j]=='S'){
               x=i;//先找到开始搜索的点
               y=j;
           }
       }
    }
    dfs(x,y);
    if(f){
        printf("yes\n");
    }else{
        printf("no\n");
    }
	return 0;
}

L - 马走日

#include<bits/stdc++.h>
using namespace std;
int f[21][22],n,m,t;
int xx[10]={-2,-2,-1,1,2,2,1,-1};//用两个数组表示方向,也就是坐标变化
int yy[10]={-1,1,2,2,1,-1,-2,-2};
int x,y,ans;
void dfs(int a,int b){
    bool p=true;
    for(int i=1;i<=n;i++){//判断是不是走过了所有点
        for(int j=1;j<=m;j++){
            if(f[i][j]==0){
                p=false;
                break;
            }
        }
    }
    if(p==true){
        ans++;
        return;
    }
    for(int i=0;i<=7;i++){
        int l=a+xx[i];
        int k=b+yy[i];
        if(f[l][k]==1)
            continue;
        if(l>0&&l<=n&&k>0&&k<=m){ 
            f[l][k]=1;
            dfs(l,k);
            f[l][k]=0;
        }
    }
}
int main()
{
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        scanf("%d %d %d %d",&n,&m,&x,&y);
        x++;
        y++;
        memset(f,0,sizeof(f));
        f[x][y]=1;
        ans=0;
        dfs(x,y);
        printf("%d\n",ans);
    }
    return 0;
}

M - 八皇后问题

#include<bits/stdc++.h>
using namespace std;
const int n = 8;
int tmp, ans;
int x[10];
int chess[10][10];
bool no(int r, int c) {//判断两个皇后的坐标是否冲突
	for (int i = 0; i < r; i++) {
		if (x[i] == c || abs(r - i) == abs(c - x[i])) 
		return false;
	}
	return true;
}
void queen(int k) {
	if (k == n) { 
	ans = max(ans, tmp);//找所有情况的八皇后的最大权值和
	return;
	}
	for (int i = 0; i < n; i++) {
		if (no(k, i)) {
			x[k] = i;//dfs前做好标记
			tmp += chess[k][i];
			queen(k + 1);
			x[k] = 0;//dfs后删除标记
			tmp -= chess[k][i];
		}
	}
}
int main() 
{
	int t;
	scanf("%d", &t);
	while (t--) {
		tmp = ans = 0;
		memset(x, 0, sizeof(x));
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++)
				scanf("%d", &chess[i][j]);
		}
		queen(0);
		printf("%d\n", ans);
	}
	return 0;
}

N - 选数

#include<bits/stdc++.h>
using namespace std;
int ans,n,k,a[50],i;
bool check(int a)
{
    int i;
    if(a<2)
	return false;
    for(i=2;i<=sqrt(a);i++)
    if(a%i==0)
	return false;
    return true;
}
void dfs(int num,int i,int sum){
    if(num==k){//输入的k个数之和是素数
        if(check(sum))
		++ans;
        return;
    }
    for(i;i<=n;i++)
    dfs(num+1,i+1,sum+a[i]);//向下一个数搜索
}
int main()
{
    cin>>n>>k;
    for(i=1;i<=n;i++)
    cin>>a[i];
    dfs(0,1,0);
    cout<<ans;
    return 0;
}

O - 打开灯泡 Switch the Lamp On

想搞懂的自行了解c++双端队列(deque)的基本操作

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,b) for(int i=a;i<=b;i++)
const int N=510;
const int dxy1[4][2]= {{1,1},{-1,-1},{1,-1},{-1,1}},dxy2[4][2]= {{1,1},{0,0},{1,0},{0,1}};
struct node
{
    int x,y;
};
int t,n,m,ans=1e8,dis[N][N];
char s[N][N];
bool vis[N][N];
deque<node> q;
int check(int x,int y)
{
    return x>=0 && x<=n && y>=0 && y<=m;
}
void bfs()
{
    vis[0][0]=1;
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    q.push_front(node {0,0});
    dis[0][0]=0;
    while(q.size())
    {
        node now=q.front();
        q.pop_front();
        fir(i,0,3)
        {
            int tx=now.x+dxy1[i][0],t1=now.x+dxy2[i][0];
            int ty=now.y+dxy1[i][1],t2=now.y+dxy2[i][1];
            int tt=(s[t1][t2] != (i<=1? '\\':'/'));
            if(check(tx,ty) && dis[tx][ty]>dis[now.x][now.y]+tt)
            {
                dis[tx][ty]=dis[now.x][now.y]+tt;
                if(tt)
                    q.push_back(node {tx,ty});
                else
                    q.push_front(node {tx,ty});
            }
        }
    }
}
int main()
{
        cin>>n>>m;
        fir(i,1,n)
        fir(j,1,m)
        cin>>s[i][j];
        bfs();
        if(dis[n][m]<1e8)
            cout<<dis[n][m]<<endl;
        else
            cout<<"NO SOLUTION"<<endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值