【寒假训练/Java】搜索专题

题目链接

搜索专题-牛客链接
比赛密码:henau202301082000

知识点

搜索剪枝
双向搜索

题目列表

快输

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        out.flush();
    }

    static class FastReader{
        BufferedReader br;
        StringTokenizer st;
        String tmp;

        public FastReader() {
            br=new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while(st==null||!st.hasMoreElements()) {
                try {
                    st=new StringTokenizer(br.readLine());
                }catch(IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong(){return Long.parseLong(next());}

        String nextLine() {
            String str="";
            try {
                str=br.readLine();
            }catch(IOException e) {
                e.printStackTrace();
            }
            return str;
        }

        boolean hasNext(){
            if(st!=null&&st.hasMoreTokens())return true;
            try {
                tmp=br.readLine();
                st=new StringTokenizer(tmp);
            }catch(IOException e) {
                return false;
            }
            return true;
        }
    }

    static PrintWriter out=new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(System.out)));
    static FastReader sc=new FastReader();
}

A - Knight Moves(BFS/双向BFS)

题意是找骑士从一个点行进到另外一个点的最少步数,可以用广搜直接找最优解,比深搜用时更少。另外还需要创建一个骑士类,来保存骑士在行进过程中的状态。

BFS解法

//骑士类
class Node{
	int x,y,step;//当前位置 (x,y) & 行进的步数
	Node(int x,int y,int step){
		this.x=x;
		this.y=y;
		this.step=step;
	}
}
	static int n,l,sx,sy,ex,ey;//骑士数量,棋盘大小,起始位置,结束位置
    static int dx[]=new int[]{-1,-1,1,1,-2,-2,2,2};
    static int dy[]=new int[]{2,-2,2,-2,1,-1,1,-1};
    static int vis[][];//该位置是否已访问
    static Queue<Node> q;//存储一个骑士在找最优解过程的所有状态
	public static void main(String[] args) {
		n=sc.nextInt();
		while(n-->0) {
			l=sc.nextInt();
			sx=sc.nextInt();sy=sc.nextInt();
			ex=sc.nextInt();ey=sc.nextInt();
			vis=new int[l][l];
			//每个骑士都需要new一个新队列来保存状态
			//也可以用数组来模拟队列,两个指针分别指向首尾
			q=new ArrayDeque<>();
			bfs(sx,sy,ex,ey);
			out.flush();
		}
	}
	
	//深搜易超时,用广搜找最优解
	static void bfs(int sx,int sy,int ex,int ey) {
		vis[sx][sy]=1;
		q.add(new Node(sx,sy,0));
		Node no;
		int x,y,step;//中间变量
		while(!q.isEmpty()) {
			no=q.poll();
			x=no.x;
			y=no.y;
			step=no.step;
			//找到则为最优解(此时step值最小)
			if(x==ex&&y==ey) {
				out.println(step);
				break;
			}
			
			//骑士可从该位置向8个方向行进,分别将对应状态存入队列中
			for(int i=0;i<8;i++) {
                int nx=x+dx[i];
                int ny=y+dy[i];
                //对于超出临界位置或者已经遍历过的位置(重复遍历意味着不可能达到最小值)跳过
                if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==1) continue;
                //该位置遍历过,需要vis数组的状态
                vis[nx][ny]=1;
                q.add(new Node(nx,ny,step+1));
			}
		}
	}

BFS-数组模拟队列

    static node q[]=new node[100005];//存储所有状态下的骑士
    static void bfs(int sx,int sy,int ex,int ey) {
        int head=1,tail=1;
        vis[sx][sy]=1;
        q[tail]=new node(sx,sy,0);
        tail++;
        while(head<tail){
            int x=q[head].x;
            int y=q[head].y;
            int step=q[head].step;
            //找到即为最优解(step值最小)
            if(x==ex&&y==ey){
                out.printf("%d\n",step);out.flush();
                break;
            }
            for(int i=0;i<8;i++){
                int nx=x+dx[i];
                int ny=y+dy[i];
                if(nx>=0&&nx<n&&ny>=0&&ny<n&&vis[nx][ny]==0)
                {
                    vis[nx][ny]=1;
                    q[tail]=new node(nx,ny,step+1);
                    tail++;
                }
            }
            head++;
        }
    }

双向BFS
这个解法就是指,从起点和终点同时出发进行搜索,二者第一次相遇的位置对应的步数即为最优解;即原本的解答树规模是 an,使用双向搜索后,规模立刻缩小到了约 2an/2 ,当n较大时优化非常可观。

浅测了一下,对于本题并没有多少优化,但是也能过。

最优解为第一次分别从起点或终点到达碰撞位置所用步数之和,需要加一个数组dis更新从终点或起点到达该碰撞位置。

以及骑士类与上相同,不再贴。

	static int n,l,sx,sy,ex,ey;//骑士数量,棋盘大小,起始位置,结束位置
    static int dx[]=new int[]{-1,-1,1,1,-2,-2,2,2};
    static int dy[]=new int[]{2,-2,2,-2,1,-1,1,-1};
    static int vis[][];//分别标记从起点/终点出发是否已访问该位置,从起点访问标记1,否则标2
    static int dis[][];//更新从对面位置出发到该碰撞点走过的距离
    static Queue<Node> qs,qe;//存储一个骑士在从起点/终点出发找最优解过程的所有状态
	public static void main(String[] args) {
		n=sc.nextInt();
		while(n-->0) {
			l=sc.nextInt();
			sx=sc.nextInt();sy=sc.nextInt();
			ex=sc.nextInt();ey=sc.nextInt();
			vis=new int[l][l];
			dis=new int[l][l];
			qe=new ArrayDeque<>();
			qs=new ArrayDeque<>();
			if(sx==ex&&sy==ey)out.println(0);
			else bfs(sx,sy,ex,ey);
			out.flush();
		}
	}
	
	static void bfs(int sx,int sy,int ex,int ey) {
		vis[sx][sy]=1;
		vis[ex][ey]=2;
		qs.add(new Node(sx,sy,0));
		qe.add(new Node(ex,ey,0));
		Node no;
		int now=0;//now表示为当前遍历回合数(保证取出的是同一行进距离的骑士)
		//从起点/终点同时出发,有一个队列为空则停止
		c:
		while(!qs.isEmpty()&&!qe.isEmpty()) {
	        while(qs.peek().step==now){
	        	no=qs.poll();
	            for(int i=0;i<8;i++){
	                int nx=no.x+dx[i];
	                int ny=no.y+dy[i];
	                
	                if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==1) continue;
	                //从终点出发遍历过该位置,此时找到最优解(step值最小)
                    if(vis[nx][ny]==2){
                        out.println(no.step+1+dis[nx][ny]);
                        break c;
                    }
                    qs.add(new Node(nx,ny,no.step+1));
                    vis[nx][ny]=1;
                    dis[nx][ny]=no.step+1;
	            }
	        }
	        
	        while(qe.peek().step==now) {
	            no=qe.poll();
	            for(int i=0;i<8;i++){
	                int nx=no.x+dx[i];
	                int ny=no.y+dy[i];
	                
	                if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==2) continue;
	                //从起点出发遍历过该位置,此时找到最优解(step值最小)
                    if(vis[nx][ny]==2){
                        out.println(no.step+1+dis[nx][ny]);
                        break c;
                    }
                    qe.add(new Node(nx,ny,no.step+1));
                    vis[nx][ny]=2;
                    dis[nx][ny]=no.step+1;
	            }
	        }
	        now++;
		}
	}

算法对比(测试平台Vjudge)

在这里插入图片描述

B - 迷宫(一)

直接深搜找可行解

	static int n,m;//棋盘大小
    static int dx[]={-1,1,0,0};
    static int dy[]={0,0,-1,1};
    static int[][] mp;//存地图
    static boolean f=false;//判断能否顺利走出迷宫
	public static void main(String[] args) {
		n=sc.nextInt();
		m=sc.nextInt();
		mp=new int[n][m];
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				mp[i][j]=sc.nextInt();
			}
		}
		dfs(0,0);
		out.println(f?"YES":"NO");
		out.flush();
	}
	
	//深搜找可行解
	static void dfs(int x,int y) {//(x,y)表示当前位置
        //排除临界情况
        if (x<0||x>=n||y<0||y>=m||mp[x][y]==1)return;
        //搜索到目标直接返回
        if (x==n-1&&y==m-1) {
            f=true;
            return;
        }
        mp[x][y]=1;//地图内可直接进行标记,经过该点标记为1
        for(int i=0;i<4;i++){
            int nx=x+dx[i];
            int ny=y+dy[i];
            dfs(nx,ny);
        }
	}

C - 迷宫(二)(BFS)

跟A题类似,除了多了一个关于钥匙开门的条件,只需要注意遍历该位置时是否带着钥匙属于不同的状态。

	static int H,W,sx,sy;//迷宫大小,起始位置
    static int dx[]={-1,1,0,0};
    static int dy[]={0,0,-1,1};
    static char[][] mp;//存迷宫
    static int vis[][][];//该位置是否已访问,第三个维度表示走过该位置是否拿着钥匙
    static Queue<Node> q;//存储找最优解过程的所有状态
	public static void main(String[] args) {
		H=sc.nextInt();
		W=sc.nextInt();
		mp=new char[H][W];
		vis=new int[H][W][2];
		q=new ArrayDeque<>();
	    for(int i=0;i<H;i++){
	       mp[i]=sc.next().toCharArray();
	        for(int j=0;j<W;j++) {
	            if(mp[i][j]=='S') {
	            	sx=i;
	            	sy=j;
	            } 
	        }
	    } 
        out.println(bfs(sx,sy));
	    out.flush();
	}
	
	//广搜找最优解
	static int bfs(int sx,int sy) {
	    vis[sx][sy][0]=1;//标记起点状态
	    q.add(new Node(sx,sy,0,0));
	    Node no;
	    while(!q.isEmpty()){
	        no=q.poll();
	        int x=no.x,y=no.y,key=no.key,step=no.step;
	        //找到终点,即找到最优解
	        if(mp[x][y]=='E') return step;
	        
	        for(int i=0;i<4;i++) {
	            int nx=dx[i]+x;
	            int ny=dy[i]+y;
	            //临界状况跳过
	            if(nx<0||nx>=H||ny<0||ny>=W)continue;
	            if(vis[nx][ny][key]==1||mp[nx][ny]=='W'||(mp[nx][ny]=='D'&&key==0))continue;
	            vis[nx][ny][key]=1;
	            q.add(new Node(nx,ny,mp[nx][ny]=='K'?1:key,step+1));//拿到钥匙则钥匙数标记为1,否则钥匙数不变
	        }
	    }
	    //没有找到通路
	    return -1;
	}

D - 老子的全排列呢

深搜过程回溯状态是因为,该题目的是求所有可行解,而判断状态的数组(pd)需要更新为当前状态。

    static int n=8;//1-8全排列
    static int[] pd=new int[n],used=new int[n];//判断某未数字是否已使用,存储当前方案的一个排列
    public static void main(String[] args) throws IOException{
        dfs(0);
    }

    static void dfs(int k) {//k为选择的第几个数字
        if(k>=n) {//条件为k==n即可,已选择n个数字,则输出该排列
        	out.print(used[0]);
            for(int i=1;i<n;i++)out.print(" "+used[i]);
            out.println();
            out.flush();
            return;
        }
        else {
            for(int i=0;i<n;i++) {
                if(pd[i]==0) {
                    pd[i]=1;
                    used[k]=i+1;
                    dfs(k+1);
                    //遍历所有方案,回溯
                    pd[i]=0;
                }
            }
        }
    }

E - [NOIP2002]选数

解法一:dfs常规解法

	static int n,k,ans=0;//共n个数,选k个,和为素数的种类数
    static int a[];//存储数列
    public static void main(String[] args) {
        n=sc.nextInt();
        k=sc.nextInt();
        a=new int[n];
        for(int i=0;i<n;i++)a[i]=sc.nextInt();
        
        dfs(0,0,0);
        out.println(ans);
        out.flush();
    }
    
    //深搜求所有可行解
    static void dfs(int cnt,int sum,int start){
    	/** @param
    	 * cnt代表现在选择了多少个数
    	 * sum表示当前的和
    	 * start表示选数起始值
    	 */
    	//选够k个数
        if(cnt==k){
            if(isprime(sum))ans++;//和为素数,结果+1
            return ;
        }
        
        //步数要加一,和也要加
        //选数起始值要变成i+1,以免算重
        for(int i=start;i<n;i++)
            dfs(cnt+1,sum+a[i],i+1);
    }
    
    //判断是否为质数
    static boolean isprime(int n){
        for(int i=2;i*i<=n;i++){
            if(n%i==0)return false;
        }
        return true;
    }

解法二:
在搜索过程中直接求素数种类总数,即将深搜目的改为返回当前素数之和是否为质数,是质数返回1,相当于返回:存在一种素数和,结果+1;

	static int n,k,ans=0;
    static int a[];
    public static void main(String[] args) throws IOException{
        // TODO Auto-generated method stub
        n=sc.nextInt();
        k=sc.nextInt();
        a=new int[n];
        for(int i=0;i<n;i++)a[i]=sc.nextInt();
        
        out.println(dfs(k,0,0));
        out.flush();
    }
    static int dfs(int k,int sum,int start){
        //k为还需要选中的个数,sum为前面累加的和
        //start和end为全组合剩下数字的选取范围;调用递归生成全组合
        //在过程中逐渐把K个数相加,当选取的数个数为0时,直接返回前面的累加和是否为质数即可
        if(k==0)return isprime(sum)?1:0;
        int cnt=0;
        for(int i=start;i<n;i++){
            cnt+=dfs(k-1,sum+a[i],i+1);
        }
        return cnt;
    }
    
    //判断是否质数
    static boolean isprime(int n){
        for(int i=2;i*i<=n;i++){
            if(n%i==0)return false;
        }
        return true;
    }

F - 素数圆环

输入的整数不超过19,最大相邻数字之和不超过37,可以直接用存储1-37是否为素数的数组进行判断。

	static int n,cnt=0;//输入数据,case数
	static boolean[] vis;//vis[i] : 标记数字i是否已访问
	static int[] ans;//存答案序列
	//判断素数(0~39)
	static int isprime[]={0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,
	            0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0};
    public static void main(String[] args) throws IOException{
    	while(sc.hasNext()) {
    		out.printf("Case %d:\n",++cnt);
            n=sc.nextInt();
            //下标从1开始
            vis=new boolean[n+1];
            ans=new int[n+1];
            ans[1]=1;
            dfs(2);//从第2个数开始,第一个数为1
            out.println();//case之间存在空行
            out.flush();
    	}
    }
    //深搜求所有可行解
    static void dfs(int k){//k指要选择的数字是第几个数
    	//选够n个数,进行输出
    	if(k>n) {
    		//判断首尾元素之和是否为素数
    		if(isprime[1+ans[k-1]]==1) {
                out.print(ans[1]);
                for(int i=2;i<=n;i++)
                    out.printf(" %d",ans[i]);
                out.println();
    		}
    		return;
    	}
    	//从数字2开始判断
    	for(int i=2;i<=n;i++) {
    		//如果这个数字访问过或者跟上一个写入的数字之和为非素数,则跳过
    		if(vis[i]||isprime[i+ans[k-1]]==0)continue;
    		vis[i]=true;
    		ans[k]=i;
    		dfs(k+1);
            //遍历所有方案,回溯
            vis[i]=false;
    	}
    }

G - Lake Counting(DFS找连通块)

题目意思就是找出有多少个连通区域。按照题目要求,如果某点为’W’(池塘),则其八个方向上同为池塘的点都算与之相连。对于搜索过的点,直接将其在数组中转为’.'(旱地),之后就不会重复搜索。

	static int n,m,ans=0;//农田大小,水田总数
    static char mp[][];//存储图
    public static void main(String[] args) throws IOException{
        n=sc.nextInt();
        m=sc.nextInt();
        mp=new char[n][m];
        for(int i=0;i<n;i++){
            mp[i]=(sc.next()).toCharArray();
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                //每一次搜索将联通的一片区域转为旱地
                if(mp[i][j]=='W'){
                    ans++;//有水方格,结果就+1
                    dfs(i,j);//传入该点坐标进行搜索
                }
            }
        }
        out.println(ans);out.flush();
    }
    public static void dfs(int x,int y){
        int nx,ny;
        //总共搜索9个点,包括本身这个点
        //为保证不重复,搜过就将该点在mp数组中标记为'.'
        for(int i=-1;i<=1;i++){
            for(int j=-1;j<=1;j++){
                nx=x+i;//横坐标
                ny=y+j;//纵坐标
                if(nx>=0&&nx<n&&ny>=0&&ny<m&&mp[nx][ny]=='W'){
                    mp[nx][ny]='.';//如果该点是'W'的话,就令该点是 '.' 以后也不访问它
                    dfs(nx,ny);// 去搜索该点
                }
            }
        }
    }

H - 水图(DFS/Dijkstra)

思路:由题目可知具有n个点和n-1条边的无向联通图是一棵树(如下图所示)。

当树具有不同的分支,则要想求从某个点出发经过该图中的每个点至少一次的最小距离,一个思路是找一条从起点出发到所有点中的一条最长路径,用所有边的权重之和(sum)的两倍减去这条路径的长度(maxLen),得到就是每个点至少经过一次所需要的最小距离(ans),即:
            ans = sum*2 - maxLen

如下例中,sum=1+2+3=6,maxLen=1+3=4,则ans=6*2-4=8,即路径1->2->3->2->4;

在这里插入图片描述

解法一:
DFS解法,Vector存邻接表,深搜求最长路径(maxLen)

//节点类
class Node{
	int v,w;//该边的另一个点,边的权重
	Node(int v,int w){
		this.v=v;
		this.w=w;
	}
}
	static int n,x;//点数,小w所处的位置(起始点)
	//存邻接表
	static Vector<Node>[] g; 
    public static void main(String[] args) {
        n=sc.nextInt();
        x=sc.nextInt();
        long ans=0;
        g=new Vector[n+1];
        //点的标记从1开始
        for(int i=1;i<=n;i++) g[i]=new Vector<>();
        	
        for(int i=1;i<n;i++) {
            int u=sc.nextInt(),v=sc.nextInt(),w=sc.nextInt();
            g[u].add(new Node(v,w));
            g[v].add(new Node(u,w));
            ans+=2*w;
        }
        out.println(ans-dfs(x,0));
        out.flush();
    }
    //深搜找从起始点出发,距离最长的一条路径(不一定经过所有点)
    static long dfs(int x,int fa){
    	//注意每一次搜索都要重置ans,这样返回给最终结果时才不会求重复
    	long ans=0;
        for(Node no:g[x]){
            if(no.v!=fa){
                ans=Math.max(ans,dfs(no.v,x)+no.w);
            }
        }
        return ans;
    }

解法二:Dijkstra
Dijkstra算法目的是为了找到起点到其它所有顶点之间的最短距离,具体实现就是利用贪心思想,每次从未遍历的点中找距离顶点最近的点加入已遍历顶点集合,其中会维护一个数组(d)存储所有点到顶点的最短距离。

本题中也可以利用Dijkstra算法求最长路径,只需要在遍历找最短路径时,将路径实际长度以相反数(正数中较小值,反而变成负数中较大值)的方式存入距离类来进行遍历,而d数组存储的依然是实际的路径长度。

//节点类
class Node{
	int v,w;//该边的另一个点,边的权重
	Node(int v,int w){
		this.v=v;
		this.w=w;
	}
}
//距离类
class Dis{
	int dis,idx;//距离,顶点下标
	Dis(int dis,int idx){
		this.dis=dis;
		this.idx=idx;
	}
}
	static int maxn=(int)1e5;
	static int n,x;//点数,小w所处的位置(起始点)
	//存邻接表
	static Vector<Node>[] g; 
    static int[] d=new int[maxn];
    public static void main(String[] args) {
        n=sc.nextInt();
        x=sc.nextInt();
        long sum=0;
        d=new int[n+1];
        //起初均标记为1e9,逐步更新距离,方便找最小值
        Arrays.fill(d,(int)1e9);
        d[x]=0;//起点到起点距离为0
        g=new Vector[n+1];
        //点的标记从1开始
        for(int i=1;i<=n;i++) g[i]=new Vector<>();
        	
        for(int i=1;i<n;i++) {
            int u=sc.nextInt(),v=sc.nextInt(),w=sc.nextInt();
            g[u].add(new Node(v,w));
            g[v].add(new Node(u,w));
            sum+=w;
        }
        
        Dijkstra();
        int mx=0;
        //找最大路径距离
        for(int i=1;i<=n;i++)
            mx=Math.max(mx,d[i]);
        out.println(sum*2-mx);
        out.flush();
    }

    static void Dijkstra(){
        Queue<Dis> q=new ArrayDeque<>();
        q.add(new Dis(-d[x],x));
        while(!q.isEmpty()) {
            int now=q.poll().idx;
            for(Node no:g[now]){
                int v=no.v;
                /** 说明:
                 * ①某点到起点的距离最小,则与之相连的点到起点的距离也应最小,
                 *  利用上面的思想,不断更新数组d的值
				 * ②数组d中的值依然是点到起点的最大距离:
				 *  因为遍历过程中是以-d[v]进行标记寻找
                 */ 
                if(d[v]>d[now]+no.w){
                    d[v]=d[now]+no.w;
                    q.add(new Dis(-d[v],v));
                }
            }
        }
    }

I - Jelly(BFS)

BFS求最优解问题。维护一个dist数组存储行进步数,也是吃到的果冻数,而当第一次访问终点时求得的值即为最优值。

//坐标类
class Node{
	int x,y,z;
	Node(int x,int y,int z){
		this.x=x;
		this.y=y;
		this.z=z;
	}
}
	static int dist[][][],n;
	static int dx[]={-1,0,1,0,0,0},dy[]={0,1,0,-1,0,0},dz[]={0,0,0,0,1,-1};
	static char ch[][][];
	static Queue<Node> q=new ArrayDeque<>();
    public static void main(String[] args) {
        n=sc.nextInt();
        dist=new int[n+1][n+1][n+1];
        ch=new char[n+1][n+1][n+1];
        for(int i=1;i<=n;i++)
        	for(int j=1;j<=n;j++)
        		ch[i][j]=(" "+sc.next()).toCharArray();
        out.println(bfs());;
        out.flush();
    }

    static int bfs() {
        for(int i=1;i<=n;i++) {
        	for(int j=1;j<=n;j++) {
        		Arrays.fill(dist[i][j],-1);
        	}
        }
        q.add(new Node(1,1,1));
        dist[1][1][1]=1;
        Node no;
        while(!q.isEmpty()){
        	no=q.poll();
            for(int i=0;i<6;i++){
                int x=no.x+dx[i],y=no.y+dy[i],z=no.z+dz[i];
                //临界情况1:超出范围
                if(x<=0||x>n||y<=0||y>n||z<=0||z>n)continue;
                //临界情况2:遇到障碍或者遍历过该点
                //后者可以保证所求结果为第一次访问求得的值,根据BFS性质可知为最优值
                if(ch[x][y][z]=='*'||dist[x][y][z]!=-1)continue;
                dist[x][y][z]=dist[no.x][no.y][no.z]+1;
                q.add(new Node(x,y,z)); 
            }
        }
        return dist[n][n][n];
    }

J - N皇后问题(DFS/位运算)

回溯法:
如果当前位置放置了一个皇后,则用三个数组分别标记对应列、对角线、反对角线的状态,标记不能在相应位置放置皇后;为得到所有可行解,需回溯这三个数组的状态。

	static int n,sum=0;//棋盘大小
	//存储空间要设置为棋盘大小的两倍,防止超出数组存储范围
    static int a[]=new int[25],b[]=new int[25],
            c[]=new int[25],d[]=new int[25];//行,列,对角线,反对角线
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        n=sc.nextInt();
        dfs(1);//得到所有结果
        out.println(sum);
        out.flush();
    }
    public static void dfs(int k){//第k行
    	//选够n个位置
        if(k>n){
            sum++;return;
        }
        else{
            for(int i=1;i<=n;i++){
                if(b[i]==0&&c[k+i]==0&&d[k+n-i]==0){
                    a[k]=i;//标记第k行取第i个数
                    b[i]=1;c[k+i]=1;d[k+n-i]=1;
                    dfs(k+1);
                    //回溯状态
                    b[i]=0;c[k+i]=0;d[k+n-i]=0;
                }
            }
        }
    }

位运算解法:
解析,是力扣上的一个解法,思路太强了,解析也很详尽,蒟蒻还需要继续学习加深理解(╥ω╥`)

	static int size,sum=0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int n=sc.nextInt();//棋盘大小 
        out.println(totalNQueens(n));
        out.flush();
    }
    
    static int totalNQueens(int n) {
      sum=0;
      size=(1<<n)-1;
      solve(0,0,0);
      return sum;
    }
    static void solve(int row, int na, int pie) {
        if (row==size) {
            sum++;
            return;
        }
        int pos=size&(~(row|na|pie));
        while (pos!=0) {
        	int p=pos&(-pos);
            pos-=p;
            solve(row|p,(na|p)<<1,(pie|p)>>1);
        }
    }

K - [NOIP2017]棋盘(BFS+优先队列优化/DFS+记忆化优化)

BFS+优先队列优化:
对本题进行分析,得到从当前位置行进方向的所有种可能(用数组dx,dy存储)以及对应方向所需要付出的魔法代价;再结合下一个格子的颜色是否不同,判断是否需要付出额外代价1;最后一个格子关于有无颜色颜色再分出两种情况。

因为行进的方向不同,也就可能具有不同的代价,所以为使BFS找到的依然是最优解,利用优先队列存储行进到每个格子需要的代价,这样在更新某个位置的代价时,就能保证为最优值。

下面代码的参考解析在

//可排序的节点类
class Node implements Comparable<Node>{
	int x,y,c,w;//(x,y)为当前位置,c为颜色
	
	Node(int x,int y,int c,int w){
		this.x=x;
		this.y=y;
		this.c=c;
		this.w=w;
	}
	Node(){}
	@Override
	public int compareTo(Node o) {
		// TODO Auto-generated method stub
		return this.w-o.w;//升序
	}
}
	static int inf=0x3f3f3f3f;
	static PriorityQueue<Node> q=new PriorityQueue<>();
	static int dx[]={0,1,0,-1,1,1,-1,-1,0,2,0,-2};//12个行进方向 
	static int dy[]={1,0,-1,0,1,-1,1,-1,2,0,-2,0};
	static int dw[]={0,0,0,0,2,2,2,2,2,2,2,2};//相应魔法代价
	static int[][] a,dis;//a存储棋盘上格子的颜色,dis存储代价
	static int n,m;
	public static void main(String[] args) {
		int x,y,c;//坐标点及各自颜色,有颜色的格子标记为1或2
		m=sc.nextInt();//棋盘的大小
		n=sc.nextInt();//盘上有颜色的格子的数量
		a=new int[m+1][m+1];
		dis=new int[m+1][m+1];
		for(int i=1;i<=n;i++){
			x=sc.nextInt();
			y=sc.nextInt();
			c=sc.nextInt();
			a[x][y]=c+1;
		}//这里c+1,为了方便区分无色格子 
		
		bfs();
		if(a[m][m]==0){//处理(m,m)没有颜色的情况
			int ans=Math.min(dis[m][m-1],dis[m-1][m])+2;
			if(ans>=inf)out.println(-1);
			else out.println(ans);
		}
		else{//处理(m,m),即终点格子有颜色的情况  
			if(dis[m][m]==inf)out.println(-1);
			else out.println(dis[m][m]);
		}
		out.flush();
	}

	static void bfs(){
		for(int i=0;i<=m;i++)Arrays.fill(dis[i],inf);
		dis[1][1]=0;
		//Node(x,y,c,w)
		q.add(new Node(1,1,a[1][1],dis[1][1]));
		Node cur;
		while(!q.isEmpty()){
			cur=q.poll();
			int x=cur.x,y=cur.y,c=cur.c,w=cur.w;
			if(dis[x][y]<w)continue;
			for(int i=0;i<12;i++){
				int nx=x+dx[i];
				int ny=y+dy[i];
				int nw=w+dw[i];
	            if(nx<=0||nx>m||ny<=0||ny>m)continue;//保证在棋盘范围内
				int nc=a[nx][ny];
				if(nc==0)continue;
				if(c!=nc)nw++;//确定下一步的信息 
				if(dis[nx][ny]>nw){
					dis[nx][ny]=nw;
					q.add(new Node(nx,ny,nc,nw));
				}
			}
		}
	}

DFS+记忆化优化(即 SPFA ):
直接搜索会超时,所以要加上记忆化剪枝优化:即在递归过程中用两个变量存储代价和魔法使用情况。

	static int m,n;
	static int inf=0x3f3f3f3f;
	static int[][] a,dis,dir={{0,1},{1,0},{0,-1},{-1,0}};
	public static void main(String[] args) {
	    m=sc.nextInt();
	    n=sc.nextInt();
	    a=new int[m+1][m+1];//color
	    dis=new int[m+1][m+1];
	    int x,y,c;
	    for(int i=1;i<=n;i++) {
	    	x=sc.nextInt();
	    	y=sc.nextInt();
	    	c=sc.nextInt();
	    	a[x][y]=c+1;
	    }
	    for(int i=0;i<=m;i++)Arrays.fill(dis[i],inf);
	    dfs(1,1,0,false);
	    if(dis[m][m]!=inf)out.println(dis[m][m]);//能走到棋盘右下角
	    else out.println(-1);
		out.flush();
	}
	//用sum记忆行进到每个状态时所需要的代价,use标记上一格是否使用了魔法
	//dis数组存更新后的最小代价
	static void dfs(int x,int y,int sum,boolean use){
	    if(sum>=dis[x][y]) return;
	    //更新最小代价
	    dis[x][y]=sum;
	    for(int i=0;i<4;i++){
	        int nx=x+dir[i][0],ny=y+dir[i][1];
	        if(nx<=0||nx>m||ny<=0||ny>m) continue;
	        if(a[nx][ny]==0){//如果格子无色
	            if(!use) {//判断有无使用过魔法
	            	a[nx][ny]=a[x][y];
	            	dfs(nx,ny,sum+2,true);//魔法代价+2
	            	a[nx][ny]=0;
	            }
	            continue;
	        }
	        if(a[nx][ny]==a[x][y])
	            dfs(nx,ny,sum,false);
	        else
	            dfs(nx,ny,sum+1,false);//不同颜色,额外消耗代价1
	    }
	}

L - 数字金字塔(DFS/DP)

DP+DFS:
相当于从金字塔底部不断向上累加最大值,金字塔顶端的值(ans[1][1])即为所求。搜索的目的就在于找到这样一条使顶端和最大的路径。

	static int n;
	static long[][] ans;
	static boolean[][] vis;
	public static void main(String[] args) {
	    n=sc.nextInt();
	    ans=new long[n+1][n+1];
	    vis=new boolean[n+1][n+1];
	    for(int i=1;i<=n;++i)
	        for(int j=1;j<=i;++j)
	            ans[i][j]=sc.nextLong();
	    out.println(dfs(1,1));
		out.flush();
	}

	static long dfs(int i,int j){
	    if(vis[i][j]||i==n)return ans[i][j];
	    ans[i][j]=ans[i][j]+Math.max(dfs(i+1,j),dfs(i+1,j+1));
	    vis[i][j]=true;
	    return ans[i][j];
	}

动归:
从上往下迭代,对于当前位置来说,其上一个位置只能为(i-1,j-1)或(i-1,j);而迭代到最后一行,这一行的值不会再随着遍历本行而更新,其对应的值含义即指以当前位置(最后一行)为结尾的路径的最大和,因此判断出其中的最大值即为所求。

	static int n;
	static long[][] ans;
	static boolean[][] vis;
	public static void main(String[] args) {
	    n=sc.nextInt();
	    ans=new long[n+1][n+1];
	    vis=new boolean[n+1][n+1];
	    for(int i=1;i<=n;++i)
	        for(int j=1;j<=i;++j)
	            ans[i][j]=sc.nextLong();
	        	
	    long max=0;
	    for(int i=2;i<=n;i++)
	        for(int j=1;j<=i;j++){
	            if(j==i)ans[i][j]+=ans[i-1][j-1];
	            else if(j>1) ans[i][j]+=Math.max(ans[i-1][j-1],ans[i-1][j]);
	            else ans[i][j]+=ans[i-1][j];
	            if(i==n&&max<ans[i][j])max=ans[i][j];
	        }
	    out.println(max);
		out.flush();
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值