CSP 201312-4 有趣的数 Java

这篇博客探讨了一道编程题目,该题目要求数字仅由0, 1, 2, 3组成,且每个数字至少出现一次,0在1前,2在3前,最高位不为0。通过分析得出最高位必为2,然后利用动态规划方法解决数位排列问题。博主给出了两种解题思路,一是基于状态转换的动态规划,二是通过枚举分析。解题代码分别用Java实现,展示了如何计算符合条件的数字组合数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目给出三个重要条件:

  1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
  2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前。
  3. 最高位数字不为0。

根据这三个条件,我们可以在得出一个结论:
4.最高位数字只能是2
这是因为最高位不能是1,因为所有的0都要在1前面。同理不能是3,因此只能为2。
并且,限制只在01之间,23之间存在

统计方案一般都要按照分情况讨论,就是对各种情况进行分类。

枚举

分析

一步步分析,最高位必须是2,接下来看n-1位
在这里插入图片描述

根据题目要求,每个数字都必须出现,那么01至少出现2次,最高出现n-2次。
假设01总共占据k个数位,那么从n-1中选取,k的取值为2<=k<=n-2
在这里插入图片描述
继续探究01,在01的数位选取完毕后,再看01的排列问题,由于所有的0都必须在1的前面,所以01所占的k个数位,必定是左边为0,右边为1的形态
在这里插入图片描述
设0的个数为t,那么1的个数为k-t
由于01至少出现一次,那么t的取值为1<=t<=k-1,因此01的排列情况为k-1种
同理,23的排列情况为n-k-1种
因此,当一个k值确定的情况下,有以下这么多的答案
在这里插入图片描述

而k值的范围,在前面已经分析出来,因此本题的答案就是

for (int k = 2; k <= n - 2; k++) {
	//C[n - 1][k]表示n-1个数位中取出k个位数的方法数
	res += (k - 1) * (n - k - 1) * C[n - 1][k];
}

由于数据量比较大,记得在相关的地方取余。

解题代码

import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        int n = input.nextInt();
        int MOD = (int)(1e9 + 7);
        int[][] C = new int[n][n];
        //这里是求组合数的一个小算法
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j <= i; j ++) {
               if (j == 0) {
                   C[i][j] = 1;
               } else {
                   C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
               }
            }
        }
        
        //此处为了方便我直接使用了长整型
        long res = 0;
        for (long k = 2; k <= n - 2; k ++) {
            res = (res + (k - 1) * (n - k - 1) * C[n - 1][(int)k]) % MOD;
        }
        
        System.out.println(res);
        
        input.close();
    }
}

状态转换 --> 动态规划

分析每个数位可能存在的状态,既然如此,那么每个状态都要满足题目的要求
那么,从最高位到最低位开始分析,应该有以下状态:


 - 只用了2, 没有用0,1,3
 - 只用了2、0,没有用1,3
 - 只用了2、3,没有用0、1
 - 只用了2、0、1,没有用3
 - 只用了2、0、3,没有用1
 - 全都用了

只有上述六种状态,至于类似用了2、1,没有用0、3,这种状态是不存在的,因为0必须在1的前面,有1肯定会有0的。

假设上面六种状态标记位状态0到状态5
假设i作为从高位到低位的第i个数字,可推导出状态关系:

s[i][0] = 1

s[i][1] = s[i - 1][0] + s[i - 1][1] * 2

s[i][2] = s[i - 1][0] + s[i - 1][2]

s[i][3] = s[i - 1][1] + s[i - 1][3] * 2

s[i][4] = s[i - 1][1] + s[i - 1][2] + s[i - 1][4] * 2

s[i][5] = s[i - 1][3] + s[i - 1][4] + s[i - 1][5] * 2

可以试着自己梳理一下,以n为6做个例子,从高位到低位,状态对应的个数为:
在这里插入图片描述
推导出关系,很容易编写代码

解题代码

import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        int MOD = (int)(1e9 + 7);
        int n = input.nextInt();
        long[][] s = new long[n + 1][6];
        
        //此处我并没有详细处理是否溢出的问题
        for (int i = 1; i <= n; i++) {
            s[i][0] = 1;
            s[i][1] = (s[i - 1][0] + s[i - 1][1] * 2) % MOD;
            s[i][2] = (s[i - 1][0] + s[i - 1][2]) % MOD;
            s[i][3] = (s[i - 1][1] + s[i - 1][3] * 2) % MOD;
            s[i][4] = (s[i - 1][1] + s[i - 1][2] + s[i - 1][4] * 2) % MOD;
            s[i][5] = (s[i - 1][3] + s[i - 1][4] + s[i - 1][5] * 2) % MOD;
        }
        
        System.out.println(s[n][5]);
        
        input.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值