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。。。。。