算法笔记方法论4 枚举法 详细笔记

寄蜉蝣于天地,渺沧海之一粟。哀吾生之须臾,羡长江之无穷。
—苏轼

说到暴力,我们大家最先想到一定是枚举,但是枚举真的是一门技术,怎么样把所有情况一个不落下的枚举出来是比较难的,所以我们这节课给大家讲解一下枚举法。

知识点

  • 简单型枚举
  • 组合型枚举
  • 排列型枚举
  • 指数型枚举

枚举法

枚举算法的思想:

将问题的所有可能成为答案的解一一列举,然后根据问题所给出的条件判断此解是否合适,如果合适就保留,反之则舍弃。

枚举算法解题的基本思路:

  1. 确定枚举解的范围,以及判断条件
  2. 选取合适枚举方法,进行逐一枚举,此时应注意能否覆盖所有的可能的解
  3. 在枚举时使用判断条件检验,留下所有符合要求的解。

枚举算法的一般步骤:

  1. 根据题目确定枚举的范围,并选取合适的枚举方式,不能遗漏任何一个真正解,同时避免重复。
  2. 为了提高解决问题的效率,看题目是否存在优化,将可能成为解的答案范围尽可能的缩小。
  3. 根据问题找到合理并、准确好描述并且好编码的验证条件。
  4. 枚举并判断是否符合第三步确定的的条件,并保存符合条件的解。
  5. 按要求输出枚举过程中留下的符合条件的解。

枚举法也是有很多技巧和方法的,这节课我们将从如下几种方法为大家进行讲解。

简单型枚举

简单型枚举就是可以通过简单的 for 循环嵌套就可以解决的问题。我们之前的课讲的题目都算是简单型枚举的范畴,所以简单型枚举是比较简单,也是大家接触最多的一种枚举方式。

这种枚举方式没有特定的固定枚举方式,而且都比较简单,按照题目的要求进行设计代码即可完成解题。

我们用一个题复习一下。

题目10 42点问题

题目描述:

众所周知在扑克牌中,有一个老掉牙的游戏叫做24点,选取4张牌进行加减乘除,看是否能得出24这个答案。
现在小蓝同学发明了一个新游戏,他从扑克牌中依次抽出6张牌,注意不是一次抽出,进行计算,看是否能够组成 42 点,满足输出YES,反之输出 NO。
最先抽出来的牌作为第一个操作数,抽出牌做第二个操作数,运算结果在当作第一个操作数,继续进行操作。
除不尽的情况保留整数。
请设计一个程序对该问题进行解答。

样例:

输入:
K A Q 6 2 3  

输出:
YES

对于上面的样例我们进行了如下计算;

1. K*A=K 即 13*1=13
2. 13/12=1 保留整数
3. 1+6=7
4. 7*2=14
5. 14*3=42

运行限制:

最大运行时间:1s
最大运行内存: 128M

代码:

public class _1024点问题 {

    static int[] a = new int[10];
    static Vector<Vector<Integer>> ans = new Vector<Vector<Integer>>();

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        for (int i=0; i<6; ++i) {
            String c;
            c = in.next();
            if (c.charAt(0) == 'A') {
                a[i] = 1;
            } else if (c.charAt(0) == 'J') {
                a[i] = 11;
            } else if (c.charAt(0) == 'Q') {
                a[i] = 12;
            } else if (c.charAt(0) == 'K') {
                a[i] = 13;
            } else {
                a[i] = (c.charAt(0) - '0');
            }
        }

        ans.addElement(new Vector<Integer>()); // 构造二维数组
        ans.get(0).addElement(a[0]); // v[0][0] = a[0]

        for (int i=1; i<=5; ++i) {
            ans.addElement(new Vector<Integer>());
            for(int j=0; j<ans.get(i-1).size(); ++j) {
                // 枚举符号
                ans.get(i).addElement(ans.get(i-1).get(j) + a[i]);
                ans.get(i).addElement(ans.get(i-1).get(j) - a[i]);
                ans.get(i).addElement(ans.get(i-1).get(j) * a[i]);
                ans.get(i).addElement(ans.get(i-1).get(j) / a[i]);
            }
        }

        int flag = 0;

        for (int i=0; i<ans.get(5).size(); ++i) {
            if (ans.get(5).get(i) == 42) {
                flag = 1;
                break;
            }
        }

        if (flag == 1) {
            System.out.println("YES");
        } else {
            System.out.println("NO");
        }
    }
}

组合型枚举

排列组合相信大家都学习过,组合型枚举就是让你在 n 个中,随机选出 m 个,问你有多少种方案,而且每一种方案选择了哪 m 个,这就是组合型枚举。

即组合型枚举就是寻找 c_{n}^m 问题。

组合型枚举有固定的流程,即有着固定的算法模板,这个需要大家去记忆一下。

public class _m01 {

    static int n; //总共n个数
    static int m; //选m个数
    static Vector<Integer> chosen = new Vector<Integer>();

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        calc(1); // 从1开始选
    }

    static <object> void calc (int x) {
        // 剪枝 1.超过所选数目 或 2.选不够 m 个
        if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
            return;
        }
        // 选够了m个数输出
        if (x == n + 1) {
            String ansTem = "";

            for (int i=0; i<chosen.size(); ++i) {
                System.out.print(chosen.get(i) + " ");
            }
            System.out.println("");
            return;
        }
        // 选x
        calc(x + 1);
        chosen.addElement(x);
        // 不选x
        calc(x + 1);
        chosen.remove((Object)x);
    }
}

题目11 公平抽签

题目描述:

小A的学校,蓝桥杯的参赛名额非常有限,只有m个名额,但是共有n个人报名,其中m<=n。作为老师非常苦恼,他不知道该让谁去,他在寻求一个绝对公平的方式。于是他准备让大家抽签决定,即m个签是去,剩下的是不去。

小A非常想弄明白最后的抽签结果是什么样子的,到底有多少种结果。

请设计一个程序帮助小A。最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。

第一行 输入 N M

第二行 到 第 N+1 行 共输入 N 个人名

每种情况输出 M 个人名,空格隔开。

样例:

输入:

3  2
xiaowang
xiaoA
xiaoli
输出:

xiaowang xiaoA
xiaowang xiaoli
xiaoA xiaoli

运行限制:

1. 最大运行时间:1s
2. 最大运行内存:128M

代码:

public class _11公平抽签 {

    static int n;
    static int m;
    static Vector<String> name = new Vector<String>();
    static Vector<String> ans = new Vector<String>();
    static Vector<Integer> chosen = new Vector<Integer>();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();

        for (int i=0; i<n; ++i) {
            String s;
            s = sc.next();
            name.addElement(s);
        }

        calc(1);

        for(int i=ans.size()-1; i>=0; --i) {
            System.out.println(ans.get(i));
        }
    }

    static void calc(int x) {

        if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
            return;
        }
        if (x == n + 1) {
            String ansTem = "";
            for (int i=0; i<chosen.size(); ++i) {
                ansTem += name.get(chosen.get(i) - 1) + " ";
            }
            ans.addElement(ansTem);
            return;
        }
        calc(x + 1);
        chosen.addElement(x);

        calc(x +1);
        chosen.removeElement((Object)x);
    }
}

排列型枚举

上面说过,组合型枚举就是让你在 n 个中,随机选出 m 个 ,问你有多少种方案,而且每一种方案选择了哪 m 个,这就是组合型枚举。

而排列型枚举相对组合型枚举就简单了一点,就是 n 个的全排列,即从 n 个中选取 n 个但是关心内部的顺序。

相比较组合只关心有多少个集合,而排列是关心集合内的排列方式。即排列型枚举就是寻找 A_{n}^n 问题。不少同学问我 20 够不够,排列问题是阶乘阶的时间复杂度,如果超过这个复杂度,那么这个题也就不用做了,算不出来。

而且排列型枚举也是有着比较成熟的模板需要大家进行记忆。

public class _m02 {

    static int n;
    static int[] order = new int[20];
    static boolean[] chosen = new boolean[20];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        calc(1);
    }

    static <object> void calc(int x) {

        if (x == n+1) { // 选够 m 个数输出
            String ansTem = "";
            for (int i=1; i<=n; ++i) {
                System.out.print(order[i] + " ");
            }
            System.out.println();
            return;
        }

        for (int i=1; i<=n; ++i) {
            if (chosen[i]) {
                continue;
            }
            // 选x
            order[x] = i;
            chosen[i] = true;

            calc(x + 1);
            
            // 不选x
            chosen[i] = false;
            order[x] = 0;
        }
    }
}

题目12 座次问题

题目描述:

小 A 的学校,老师好不容易解决了蓝桥杯的报名问题,现在老师又犯愁了。现在有 N 位同学参加比赛,但是老师想给他们排座位,但是排列方式太多了。老师非常想弄明白最后的排座次的结果是什么样子的,到底有多少种结果。

请设计一个程序帮助老师。

最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。

第一行 输入 N;
第二行 到 第N+1 行 共输入 N 个人名。

由于小 A 学校承办能力实在有限,所以其中 N 小于等于 10 人。

样例:

输入:

3
xiaowang
xiaoA
xiaoli
输出:

xiaowang xiaoA xiaoli
xiaowang xiaoli xiaoA
xiaoA xiaowang xiaoli
xiaoA xiaoli xiaowang
xiaoli xiaowang xiaoA
xiaoli xiaoA xiaowang

运行限制:

1. 最大运行时间:1s
2. 最大运行内存:128M

代码:

public class _12座次问题 {

    static int n;
    static int[] order = new int[20];
    static boolean[] chosen = new boolean[20];
    static Vector<String> name = new Vector<String>();

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();

        for (int i=0; i<n; ++i) {
            String s;
            s = sc.next();
            name.addElement(s);
        }

        calc(1);
    }

    static <object> void calc(int x) {
        if (x == n+1) {
            String ansTem = "";

            for (int i=1; i<=n; ++i) {
                ansTem += name.get(order[i] - 1)+ " ";
            }

            System.out.println(ansTem);
            return;
        }

        for (int i=1; i<=n; ++i) {
            if (chosen[i]) {
                continue;
            }
            order[x] = i;
            chosen[i] = true;

            calc(x + 1);

            order[x] = 0;
            chosen[i] = false;
        }
    }
}

你可能感兴趣的:(算法笔记,算法,蓝桥杯)