2022国赛java第二题知识点:组合数学

2022国赛java第二题知识点:组合数学

预备题: 牡牛和牝牛、方程的解、车的放置、数三角形、考新郎

真题:小蓝与钥匙

一.牡牛和牝牛

原题链接

https://www.acwing.com/problem/content/1309/

1. 题意

长度为n的01字符串,要求两个1之间至少有k个0,求满足要求的字符串数量。

2. 思路

f[i]:最后一个1位置为i的字符串数量。

f[i] = f[0] + f[1] + … f[i - k - 1]。 至少加一个f[0],因为f[0]表示前面全0的状态。

在这里插入图片描述

当间隔k个时,右下标为i,左下标为i-k-1

3. 边界值

如何设置边界f[0]的初值?

如果由边界点的初值转移的值是正确的,则初值是正确的。

例如此题 f[1] = f[0] = 1即 1000 = 0000的个数,所以初值正确。

4.代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    static int N = 100010, mod = 5000011;
    static int n, m, k;
    static int[] f = new int[N], s = new int[N];
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        n = Integer.parseInt(s1[0]);
        k = Integer.parseInt(s1[1]);
        f[0] = 1;
        s[0] = 1;
        for (int i = 1; i <= n; i ++ ) {
            f[i] = s[Math.max(0, i - k - 1)];
            s[i] = (s[i - 1] + f[i]) % mod;
        }
        System.out.println(s[n]
        );
    }
}

二.方程的解

原题链接https://www.acwing.com/problem/content/1310/

1. 题意

给定一个数分成k个数的和,问有多少种方案。

2. 思路

隔板法。
在这里插入图片描述

每个小球认为是数字1,每个数ai认为是空间内的小球数量和。

这样就变成了n-1个空隙里插k-1个板子 = c[n-1] [k -1]

再套个求组合数的模板

3. 代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class Main {
    static int N = 1010;
    static int x, k;
    static BigInteger[][] c = new BigInteger[1010][1010];
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        k = Integer.parseInt(s1[0]);
        x = Integer.parseInt(s1[1]);
        int m = k - 1;
        int n = qmi(x, x, 1000);
        for (int i = 0; i <= n;  i++ ) {
            for (int j = 0; j <= n; j ++) {
                c[i][j] = new BigInteger("0");
            }
        }
        for (int i = 0; i <= n; i ++ ) {
            for (int j = 0; j <= i; j ++ ) {
                if (j == 0) c[i][j] = new BigInteger("1");
                else c[i][j] = c[i - 1][j].add(c[i - 1][j - 1]);
            }
        }
        System.out.println(c[n - 1][m]);

    }
        public static int qmi (int m, int k, int p) {
        BigInteger res = new BigInteger("1");
        BigInteger t = new BigInteger(String.valueOf(m));
        BigInteger mod = new BigInteger(String.valueOf(p));
        while (k > 0) {
            if ((k & 1) == 1) res = res.multiply(t).mod(mod);
            t = t.multiply(t).mod(mod);
            k >>= 1;
        }
        return res.intValue();
    }
}

4. 求组合数模板

void init()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<=i;j++)
            if(!j) c[i][j]=1;//注意
            else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}

三. 车的放置

原题链接https://www.acwing.com/problem/content/1311/

1. 题意

给定一个L型棋盘,放k个棋子,要求每个棋子不能放在同一行同一列,求方案数

2. 思路

让我们先研究下规则的矩形棋盘。

在这里插入图片描述

如图是个4*4的矩形,若放置3个棋子,则在4行里选三行C[4] [3]。

然后在4列里选3列,由于列的选择是有序的
在这里插入图片描述

先选择第一列后选择第二列和先选择第二列后选择第一列方案不同。

所以是排列数A[4] [3]。

最后答案为C[4] [3] * A[4] [3]。

但题目是L型棋盘,我们只需要切分成两个矩形,然后枚举每个矩形的棋子个数。

3. 代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class Main {
    static int N = 2010;
    static int a, b, c, d, k;
    static int mod = 100003;
    static long[][] C = new long[N][N], P = new long[N][N];
    static long[] fa = new long[N];
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        a = Integer.parseInt(s1[0]);
        b = Integer.parseInt(s1[1]);
        c = Integer.parseInt(s1[2]);
        d = Integer.parseInt(s1[3]);
        k = Integer.parseInt(s1[4]);
        int n = Math.max(a, b);
        n = Math.max(n, d);
        n = Math.max(a + c, n);
        fa[0] = 1;//0的阶乘是1
        for (int i = 1; i <= n; i ++ ) {//阶乘
            fa[i] = i * fa[i - 1] % mod;
        }
        init(n);
        long res = 0;
        for (int i = 0; i <= k; i ++ ) {
            if (i > Math.min(a, b)) continue;
            if (k - i > Math.min(a + c - i, d)) continue;
            res = (res + C[b][i] * P[a][i] % mod * C[d][k - i] % mod * P[a + c - i][k - i] % mod) % mod;
        }
        System.out.println(res);
    }
    public static void init (int n) {
    //n^2求组合数
        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;
            }
        }
        //排列数A[n][m] = C[n][m] * m!
        for (int i = 0; i <= n; i ++ ) {
            for (int j = 0; j <= i; j++) {
                if (j == 0) P[i][j] = 1;
                else P[i][j] = C[i][j] * fa[j] % mod;
            }
        }
    }
}

四. 数三角形

原题链接:https://www.acwing.com/problem/content/1312/

1. 题意

在棋盘中选定三个点组成三角形,问能组成多少三角形。

2. 思路

在所有点中任选三个点,然后减去不合法的点。

同一条横线的三个点C[n] [3]。

同一条竖线的三个点C[m] [3]。

同一条斜线上的三个点:k>0为例

斜线上的左端点与右端点的横坐标之差为i,纵坐标之差为j。

则斜线上的点个数为gcd(i, j) -1. (不包括左右端点)

同时棋盘中这样的线个数为(m - i + 1) + (n - j + 1)。

则同一斜线上三个点为2 * (gcd(i, j) - 1) * (m - i + 1) + (n - j + 1)

3. 代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class Main {
    static int N = 1010;
    static long[][] c = new long[N * N][4];
    static int n, m;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        n = Integer.parseInt(s1[0]);
        m = Integer.parseInt(s1[1]);
        m ++ ;
        n ++ ;
        init();
        long res = c[m * n][3] - n * c[m][3] - m * c[n][3];
        //枚举横坐标差和纵坐标差
        long sum = 0;
        m -- ;
        n -- ;
        for (int i = 1; i <= m; i ++ ) {
            for (int j = 1; j <= n; j ++ ) {
                sum += 2 * (long)(gcd(i, j) - 1) * (n - j + 1) * (m - i + 1);
            }
        }
        System.out.println(res - sum);
    }
    public static int gcd (int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    public static void init () {
        int K = m * n;
        for (int i = 0; i <= K; i ++ ) {
            for (int j = 0; j <= i && j <= 3; j ++ ) {
                if (j == 0) c[i][j] = 1;
                else c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
            }
        }
    }
}

五. 考新郎

原题链接

http://acm.hdu.edu.cn/showproblem.php?pid=2049

1. 题意

共有N对夫妇,有M个新郎找错了新娘,问找错新娘的方案数。

2. 思路

错排应用: 将n封信投入到n个邮箱中,问所有信都投错信封的方案数。

这题从N对夫妇中选M个排错的新郎C[n] [m],再乘以错排方案数D[m]。

3. 错排模板

https://www.acwing.com/blog/content/3072/

4. 代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class Main {
    static int N = 25;
    static long[] D = new long[N];
    static long[][] c = new long[N][N];
    static int n, m;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        int T = Integer.parseInt(s1[0]);
        init();
        while (T -- > 0) {
            String[] s2 = br.readLine().split(" ");
            n = Integer.parseInt(s2[0]);
            m = Integer.parseInt(s2[1]);
            System.out.println(c[n][m] * D[m]);
        }

    }
    public static void init () {
        D[1] = 0;
        D[2] = 1;
        for (int i = 3; i <= 20; i ++ )
            D[i] = (i - 1) * (D[i - 1] + D[i - 2]);
        for (int i = 0; i <= 20; 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];
            }
        }
    }
}

六.国赛第二题小蓝与钥匙

原题链接:https://blog.csdn.net/qq_43449564/article/details/125449981

1. 题意

与选新郎一模一样

2. 思路

注意错排公式D[20]就已经有18位了,所以数据范围一旦超过20就要考虑用大整数

3. 代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class Main {
    static int N = 30;
    static BigInteger[] D = new BigInteger[N];
    static long[][] c = new long[N][N];
    static int n, m;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        init();
        BigInteger C = new BigInteger(String.valueOf(c[28][14]));
        System.out.println(C.multiply(D[14]));
    }
    public static void init () {
        D[1] = new BigInteger("0");
        D[2] = new BigInteger("1");
        for (int i = 3; i < N; i ++ ) {
            D[i] = new BigInteger("0");
        }
        for (int i = 3; i < N; i ++ )
            D[i] = (D[i - 1].add(D[i - 2]).multiply(new BigInteger(String.valueOf(i - 1))));
        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];
            }
        }
    }
}

本专题内容正在努力更新ing。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值