蓝桥杯之垒骰子

题目描述
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。

经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!

我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。

假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 10^9 + 7 的结果。
不要小看了 atm 的骰子数量哦~

示例
输入第一行两个整数 n m;
n表示骰子数目;
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36

例如:
用户输入:
2 1
1 2
输出一行一个数,表示答案模 10^9 + 7 的结果。
例如:
对应输出:
544

暴力解法(dfs):

import java.util.Scanner;
public class LanQiao36 {
    static int res = 0;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();// n个骰子
        int m = sc.nextInt();// m对互斥关系
        int[][] nums = new int[m][2];// 存储每一组互斥关系
        for(int i = 0; i < m; i++){
            for(int j = 0; j < 2; j++){
                nums[i][j] = sc.nextInt();
            }
        }
        int[] dianShu = {1, 2, 3, 4, 5, 6};
        dfs(n, nums, dianShu, 0, 1);
        long ans = (long)(res * Math.pow(4, n)) % 1000000007;
        System.out.println(ans);
    }
    public static void dfs(int n, int[][] nums, int[] dianShu, int k, int temp){//temp 存储上一个骰子的顶面点数是多少
        if(k == n){
            res++;
            return;
        }
        for(int i = 0; i < dianShu.length; i++){
            //当 k 大于等于 1 的时候,即到了第二个骰子的时候才用和上一个骰子比较是否出现互斥,只有第一个骰子是没办法比的
            //第一次做的时候没想出来如何实现以上思路,但后来发现只要加上 k >= 1就行
            if(k >= 1){
                if(isValid(nums, temp, getDuiLi(dianShu[i]))){
                    dfs(n, nums, dianShu, k + 1, dianShu[i]);
                }
            }else{
                dfs(n, nums, dianShu, k + 1, dianShu[i]);
            }
        }
    }
    public static boolean isValid(int[][] nums , int x, int y){//判断是否互斥
        for(int[] arr : nums){
            if((arr[0] == x && arr[1] == y) || (arr[0] == y && arr[1] == x)){
                return false;
            }
        }
        return true;
    }
    public static int getDuiLi(int num){//取得一个骰子上面点数的对立面的点数
        if(num <= 3){
            return num + 3;
        }
        return num - 3;
    }
}

将判断是否互斥的语句改进一下:

import java.util.Scanner;

public class LanQiao37 {
    static int res = 0;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();// n个骰子
        int m = sc.nextInt();// m对互斥关系
        boolean[][] nums = new boolean[6][6];// 存储每一组互斥关系,i代表第一个骰子,j代表第二个骰子
        for (int i = 0; i < m; i++) {
            int num1 = sc.nextInt();
            int num2 = sc.nextInt();
            //注意:都要赋成true
            nums[num1 - 1][num2 - 1] = true;
            nums[num2 - 1][num1 - 1] = true;
        }
        int[] dianShu = {1, 2, 3, 4, 5, 6};
        dfs(n, nums, dianShu, 0, 1);
        long ans = (long) (res * Math.pow(4, n)) % 1000000007;
        System.out.println(ans);
    }

    /**
     * @param n       n个骰子
     * @param nums    存储第一个骰子和第二个骰子互斥的点数
     * @param dianShu 骰子的点数
     * @param k       记录是第几个骰子
     * @param temp    存储上一个骰子的顶面点数是多少
     */
    public static void dfs(int n, boolean[][] nums, int[] dianShu, int k, int temp) {
        if (k == n) {
            res++;
            return;
        }
        for (int i = 0; i < dianShu.length; i++) {
            //当 k 大于等于 1 的时候,即到了第二个骰子的时候才用和上一个骰子比较是否出现互斥,只有第一个骰子是没办法比的
            //第一次做的时候没想出来如何实现以上思路,但后来发现只要加上 k >= 1就行
            if (k >= 1) {
                if (!nums[temp - 1][getDuiLi(dianShu[i]) - 1]) {
                    dfs(n, nums, dianShu, k + 1, dianShu[i]);
                }
            } else {
                dfs(n, nums, dianShu, k + 1, dianShu[i]);
            }
        }
    }

    public static int getDuiLi(int num) {//取得一个骰子上面点数的对立面的点数
        if (num <= 3) {
            return num + 3;
        }
        return num - 3;
    }
}

动态规划法:

import java.util.Scanner;

public class LanQiao35 {
    private static int[][] a = new int[7][7];//存放6个面的排斥关系,只用到数组下标1~6
    private static long count;
    private static final long C = 1000000007;

    public static void main(String[] args) {
        int num = 4;
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for (int i = 0; i < m; i++) {
            int a1 = sc.nextInt();
            int a2 = sc.nextInt();
            a[a1][a2] = 1;
            a[a2][a1] = 1;
        }
        //滚动数组(只用了两层)
        int dp[][] = new int[2][7];//dp[i][j]表示某一高度的骰子j面朝上的方案数
        int e = 0;
        for (int i = 1; i < 7; i++) {
            dp[e][i] = 1;//初始化 已知高度为1的骰子的方案只有一种,此处忽略结果×4的情况,在下面加上
        }
        for (int i = 2; i <= n; i++) {//从第二颗骰子算,到第n颗
            e = 1 - e;
            num = (int) ((num * 4) % C);
            for (int j = 1; j < 7; j++) {//遍历当前骰子各面
                dp[e][j] = 0;//初始化下一颗骰子j面朝上的方案数为0
                for (int k = 1; k < 7; k++) {//遍历之前一颗骰子的6个面
                    if (!isValid(k, getDuiLi(j))) {//不相互排斥,k为之前下方骰子的朝上面,getDuiLi(j)为当前骰子朝上面的对立面,即朝下面
                        dp[e][j] += dp[1 - e][k];
                    }
                }
                dp[e][j] = (int) (dp[e][j] % C);
            }
        }
        for (int i = 1; i < 7; i++) {
            count += dp[e][i];
            count = count % C;
        }
        count = (count * num) % C;//这个地方相乘后仍然很大,是这个算法的弊端
        //count = (count * Math.pow(4, n)) % C;
        System.out.println(count);
    }

    public static int getDuiLi(int num) {//取得一个骰子上面点数的对立面的点数
        if (num <= 3) {
            return num + 3;
        }
        return num - 3;
    }

    private static boolean isValid(int current, int last) {
        if (a[current][last] == 1) {//说明两个骰子互相排斥
            return true;
        }
        return false;
    }
}

非滚动数组:

import java.util.Scanner;

public class LanQiao34_05 {
    private static int[][] a = new int[7][7];//存放6个面的排斥关系,只用到数组下标1~6
    private static long count;
    private static final long C = 1000000007;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for (int i = 0; i < m; i++) {
            int a1 = sc.nextInt();
            int a2 = sc.nextInt();
            a[a1][a2] = 1;
            a[a2][a1] = 1;
        }
        int num = 4;
        int[][] dp = new int[n + 1][7];//dp[i][j]表示高度为i的骰子j面朝上的方案数
        for(int j = 1; j < 7; j++){
            dp[1][j] = 1;
        }
        for(int i = 2; i <= n; i++){
            num = (num * 4) % (int)C;
            for(int j = 1; j < 7; j++){//遍历当前骰子的 1 到 6 的点数
                for(int k = 1; k < 7; k++){//遍历上一个骰子的 1 到 6 的点数
                    if(!isValid(getDuiLi(j), k)){
                        dp[i][j] += dp[i - 1][k];
                    }
                }
                dp[i][j] = dp[i][j] % (int)C;
            }
        }
        for(int j = 1; j < 7; j++){
            count += dp[n][j];
            count = count % C;
        }
        count = (count * num) % C;//这个地方相乘后仍然很大,是这个算法的弊端
        //count = (count * Math.pow(4, n)) % C;
        System.out.println(count);
    }

    public static int getDuiLi(int num) {//取得一个骰子上面点数的对立面的点数
        if (num <= 3) {
            return num + 3;
        }
        return num - 3;
    }

    private static boolean isValid(int current, int last) {
        if (a[current][last] == 1) {//说明两个骰子互相排斥
            return true;
        }
        return false;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

静波波呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值