初级DFS练习:POJ1111 POJ1129 POJ2245

POJ1111

[题目链接]( 1111 -- Image Perimeters (poj.org) )

详细代码:

package poj1111;
import java.util.Scanner;
/**
 * @Author jinjun99
 * @Date Created in 2021/11/27 9:49
 * @Description 题目大意:二维数组表示一张X光照片的像素点,
 * 一个对象的图案对应的像素点用X表示,其他空间用.表示
 * 鼠标点击一个对象中的某个像素点X时,会选中该对象
 * 要求算出该对象的边缘周长。
 * 解题思路:用DFS遍历选中对象所有的像素点,
 * 碰到上下左右四个方向的边界时周长+1。
 * @Since version-1.0
 */
public class Main {
    /**
     * 行
     */
    static int r;
    /**
     * 列
     */
    static int c;
    /**
     * 初始坐标
     */
    static int x;
    static int y;
    /**
     * 图像数组
     */
    static char[][] img;
    /**
     * 标记数组
     */
    static int[][] mark;
    /**
     * 周长
     */
    static int perimeter;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()){
            String str = sc.nextLine();
            String[] s = str.split(" ");
            r = Integer.parseInt(s[0]);
            c = Integer.parseInt(s[1]);
            x = Integer.parseInt(s[2])-1;
            y = Integer.parseInt(s[3])-1;
            if (r==0 && c==0 && x==-1 && y==-1) {
                break;
            }
            img = new char[r][c];
            mark = new int[r][c];
            for (int i = 0; i < r; i++) {
                String s1 = sc.nextLine();
                char[] c1 = s1.toCharArray();
                for (int j = 0; j < c; j++) {
                    img[i][j] = c1[j];
                }
            }
            perimeter = 0;
            dfs(x,y);
            System.out.println(perimeter);
        }
    }
    /**
     * 方向数组
     */
    private static int[] dirX = {0,1,0,-1,-1,1,-1,1};
    private static int[] dirY = {1,0,-1,0,-1,1,1,-1};
    private static void dfs(int x, int y) {
        /*dfs所有'X'*/
        if (img[x][y]=='X'&&mark[x][y]==0){
            mark[x][y]=1;
            /*8个前进搜索方向*/
            for (int i = 0; i < 8; i++) {
                int a = x+dirX[i];
                int b = y+dirY[i];
                boolean bl1 = a>=0&&b>=0&&a<r&&b<c;
                /*如果出界或者是'.',则当前子树结束搜索*/
                if (!bl1 || img[a][b] == '.'){
                    /*如果是前4个方向出界,则累计一个单位的边长*/
                    if (i<4){
                        perimeter++;
                    }
                }else {
                    dfs(a,b);
                }
            }
        }
    }
}
/*
输入:
2 2 2 2
XX
XX
6 4 2 3
.XXX
.XXX
.XXX
...X
..X.
X...
5 6 1 3
.XXXX.
X....X
..XX.X
.X...X
..XXX.
7 7 2 6
XXXXXXX
XX...XX
X..X..X
X..X...
X..X..X
X.....X
XXXXXXX
7 7 4 4
XXXXXXX
XX...XX
X..X..X
X..X...
X..X..X
X.....X
XXXXXXX
0 0 0 0
输出:
8
18
40
48
8
*/

POJ1129

[题目链接]( 1129 -- Channel Allocation (poj.org) )

详细代码:

package poj1129;
import java.util.Scanner;
/**
 * @Author jinjun99
 * @Date Created in 2021/11/25 17:49
 * @Description 题目大意:广播需要使用多个中继器扩大范围,相邻的中继器不能使用相同的频道以免干扰
 * 给出一组中继器网络,标记出该网络中每个中继器分别和哪些中继器相邻。问最少需要几个频道才能互不干扰。
 * 解题思路:四色问题,频道数范围是2-4,用DFS依次搜索2-4个频道下是否干扰即可。
 * @Since version-1.0
 */
public class Main {
    /**
     * 中继器数量
     */
    static int n;
    /**
     * 中继器地图数组
     */
    static int[][] map;
    /**
     * 频道分配数组
     */
    static int[] distribute;
    /**
     * 用来记录频道数为2,3,4时分别的分配方案数
     */
    static int[] scheme = new int[3];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            /*获取第一个数字,即中继器数量*/
            n = sc.nextInt();
            /*如果数量为0,则结束*/
            if (n == 0) {
                break;
            }
            /*初始化中继器地图数组和频道数组*/
            map = new int[n][n];
            distribute = new int[n];
            scheme = new int[3];
            /*是否有相邻的边*/
            boolean side = false;
            /*接收一组数据输入,给数组赋值*/
            for (int i = 0; i < n; i++) {
                String str = sc.next();
                /*每个中继器按字母表顺序用字母表示,可以用第i个字母-第一个
                字母A,利用ASCII码之差得到对应字母在字母表中的序数,*/
                int charNum = str.charAt(0) - 'A';
                /*获取第一个字母表示的中继器相邻的其他中继器*/
                /*跳过冒号从2开始*/
                for (int j = 2; j < str.length(); j++) {
                    /*获取字母对应的序数*/
                    int adja = str.charAt(j) - 'A';
                    map[charNum][adja] = 1;
                    map[adja][charNum] = 1;
                    side = true;
                }
            }
            /*频道数*/
            int channel = 1;
            if (side) {
                dfs(0, 0, distribute);
                for (int i = 0; i < 3; i++) {
                    if (scheme[i] != 0) {
                        channel = i + 2;
                        System.out.println(channel + " channels needed.");
                        break;
                    }
                }
            } else {
                System.out.println("1 channel needed.");
            }


        }
    }

    /**
     * @param r 当前中继器编号
     * @param c 当前子树能使用的频道数
     * @param d 当前的频道安排情况
     * @return
     */
    private static void dfs(int r, int c, int[] d) {
        if (r == 0) {
            for (int i = 2; i <= 4; i++) {
                d = new int[n];
                dfs(1, i, d);
            }
        } else {
            /*当4个中继器遍历完*/
            if (r == n + 1) {
                /*对应限制c个频道的方案数+1*/
                scheme[c - 2]++;
                return;
            }
            /*遍历当前能使用的频道代号*/
            for (int i = 1; i <= c; i++) {
                /*对第r个中继器安排i频道*/
                d[r - 1] = i;
                /*判断条件*/
                if (satisfied(r, c, d)) {

                    dfs(r + 1, c, d);
                }
                /*回溯省略,下一层循环自动刷新*/
            }
        }


    }

    /**
     * @param r 当前中继器编号
     * @param c 当前子树能使用的频道数
     * @param d 当前的频道安排情况
     * @return
     */
    private static boolean satisfied(int r, int c, int[] d) {
        /*剪枝条件,如果当前限制的频道数(或前一个限制)还未找到一个符合条件的方案*/
        boolean condition = (c == 2 && scheme[c - 2] == 0) || (c > 2 && scheme[c - 2] == 0 && scheme[c - 3] == 0);
        if (condition) {
            /*遍历地图找到当前中继器邻接的其他中继器,依次对比是否符合条件*/
            for (int i = 0; i < n; i++) {
                /*相邻的中继器频道不同*/
                if (map[r - 1][i] == 1 && d[r - 1] == d[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
}
/*
输入:
2
A:
B:
4
A:BC
B:ACD
C:ABD
D:BC
4
A:BCD
B:ACD
C:ABD
D:ABC
0
输出:
1 channel needed.
3 channels needed.
4 channels needed.
*/

POJ2245

[题目链接]( 2245 -- Lotto (poj.org) )

详细代码:

package poj2245;

import java.util.ArrayList;
import java.util.Scanner;

/**
 * @Author jinjun99
 * @Date Created in 2021/11/26 12:52
 * @Description 题目大意:在德国乐透中你需要从一个集合[1,2..49]中选中6个数字,
 * 一种流行的策略是先从原始集合中选出k(k>6)个数字的子集S,再从子集中选择6个数字
 * 输入包含一个k和对应的子集S,要求按字典序打印从S中选6个数的所有可能。
 * 解题思路:其实就是求组合C(n=k,m=6),字典序就是限制每一位数比前一位大
 * @Since version-1.0
 */
public class Main {
    /**
     * 集合长度
     */
    static int k;
    /**
     * 集合
     */
    static int[] set;

    /**
     * 固定取6个数
     */
    static int m = 6;
    /**
     * 所有子集
     */
    static ArrayList<int[]> sub;

    /**
     * 当前子集
     */
    static int[] currSub;
    /**
     * 被选过的数
     */
    static boolean[] selected;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
//        long st = System.currentTimeMillis();
        while (sc.hasNext()){
            /*数据输入和初始化*/
            String str = sc.nextLine();
            String[] s1 = str.split(" ");
            k = Integer.parseInt(s1[0]);
            if (k==0){
                break;
            }
            set = new int[k];
            currSub = new int[m];
            selected = new boolean[k];
            sub = new ArrayList<int[]>();
            for (int i = 0; i < k; i++) {
                set[i] = Integer.parseInt(s1[i+1]);
            }
            /*搜索*/
            dfs(0);
            /*打印*/
            for (int i = 0; i < sub.size(); i++) {
                int[] subSetI = sub.get(i);
                for (int j = 0; j < m; j++) {
                    if (j==m-1){
                        System.out.println(subSetI[j]);
                    }else {
                        System.out.print(subSetI[j]+" ");
                    }
                }
            }
            System.out.println("");
        }

//        long e = System.currentTimeMillis();
//        System.out.println("计算时长:"+(e-st)+"ms");
    }

    /**
     * @param l 解空间树的层数,当前子集的第几个数
     */
    private static void dfs(int l) {
        if (l==m){
            /*到达叶子节点,放入子集集合中*/
            int[] sub1 = new int[m];
            System.arraycopy(currSub, 0, sub1, 0, m);
            sub.add(sub1);
            return;
        }
        /*遍历k个数*/
        for (int i = 0; i < k; i++) {
            /*剪枝:第一个数的下标不能大于k-m,否则后面选出来的都不符合字典序*/
            if (l==0&&k-i<6){
                return;
            }
            /*当前set[i]没被选过并且大于当前子集的前一个数(字典序)*/
            boolean bl = !selected[i]&&(l==0||(l>0&&set[i]>currSub[l-1]));
            if (bl){
                /*填入当前子集,并锁定set中的当前数字*/
                currSub[l]=set[i];
                selected[i]=true;
                dfs(l+1);
                /*回溯*/
                selected[i]=false;
                currSub[l]=0;
            }
        }
    }
}
/*
输入:
7 1 2 3 4 5 6 7
8 1 2 3 5 8 13 21 34
0
输出:
1 2 3 4 5 6
1 2 3 4 5 7
1 2 3 4 6 7
1 2 3 5 6 7
1 2 4 5 6 7
1 3 4 5 6 7
2 3 4 5 6 7

1 2 3 5 8 13
1 2 3 5 8 21
1 2 3 5 8 34
1 2 3 5 13 21
1 2 3 5 13 34
1 2 3 5 21 34
1 2 3 8 13 21
1 2 3 8 13 34
1 2 3 8 21 34
1 2 3 13 21 34
1 2 5 8 13 21
1 2 5 8 13 34
1 2 5 8 21 34
1 2 5 13 21 34
1 2 8 13 21 34
1 3 5 8 13 21
1 3 5 8 13 34
1 3 5 8 21 34
1 3 5 13 21 34
1 3 8 13 21 34
1 5 8 13 21 34
2 3 5 8 13 21
2 3 5 8 13 34
2 3 5 8 21 34
2 3 5 13 21 34
2 3 8 13 21 34
2 5 8 13 21 34
3 5 8 13 21 34
*/
posted @ 2022-11-07 22:42  矜君  阅读(28)  评论(0编辑  收藏  举报