为了更好的阅读体检,可以查看我的算法学习网
本题在线评测链接:P1162
前言
今天塔子哥要与大家分享的是塔子哥认为去年机考所有题目中难度最高的一道题。拿到ACM区域赛中也能算是一个铜牌+的题了。要想在1个小时内分析完成并满分通过还是有一定的挑战。但是它当时被放在了第一题,只值100分… , 所以塔子哥认为是相当的抽象了。没打过acm不会做或者拿不满分是很正常的,可以看看题解区,塔子哥给了一个解法,或许对你能有一些帮助!
题目内容
有 M M M 个位置(使用 0 , 1 , 2 , . . . , M − 1 0,1,2,...,M-1 0,1,2,...,M−1方式连续编号),每种物品(物品使用 0 , 1 , 2 , . . . , N − 1 0,1,2,...,N-1 0,1,2,...,N−1 方式连续编号)需要种植到其中一个位置上 ,
要求物品编号除以总位置数的余数不能与所放置的位置编号相同,放置方案要满足被放置的位置数目尽量多,多个位置中的物品不能重样。
塔子哥想问问所有满足条件的物品放置方案数目。
输入描述
输入为两个整数,分别是位置个数 M M M 和 物品种类数 N N N 。
1 ≤ M ≤ 1000 1 \leq M \leq 1000 1≤M≤1000 , 1 ≤ N ≤ 10000 1 \leq N \leq 10000 1≤N≤10000
输出描述
输出满足条件的物品放置方案数目
样例
输入
2 3
输出
2
1.前置知识:错排原理的容斥解法: 错排数 = 所有排列的个数 - 至少存在一个对位的排列个数 + 至少存在两个对位的排列个数 - 至少存在三个对位的排列个数 + …
即
D
(
n
)
=
n
!
−
C
n
1
(
n
−
1
)
!
+
C
n
2
(
n
−
2
)
!
−
.
.
.
+
(
−
1
)
n
C
n
n
0
!
D(n) = n! - C_{n}^{1}(n - 1)! + C_{n}^{2}(n - 2)! -... +(-1)^n C^{n}_{n} 0!
D(n)=n!−Cn1(n−1)!+Cn2(n−2)!−...+(−1)nCnn0!
2.考虑一个简单情况:当
n
=
m
n = m
n=m 这就是一个错排问题。
3.当
n
<
m
n < m
n<m时 , 显然可以沿用容斥方法得出公式:
D
(
m
,
n
)
=
A
m
n
−
C
n
1
A
m
−
1
n
−
1
+
C
n
1
A
m
−
1
n
−
1
−
.
.
.
+
(
−
1
)
n
C
n
n
A
m
−
n
0
D(m , n) = A_{m}^{n} - C_{n}^{1}A_{m - 1}^{n - 1} + C_{n}^{1}A_{m - 1}^{n - 1} - ... + (-1)^n C_{n}^{n}A_{m - n}^{0}
D(m,n)=Amn−Cn1Am−1n−1+Cn1Am−1n−1−...+(−1)nCnnAm−n0
4.当
n
>
m
n > m
n>m 时,情况会比较复杂:
举个例子: n = 4 , m = 2 n = 4, m = 2 n=4,m=2,如下图所示:
合法的排列就是:
(1 , 0) , (1 , 2), (1 , 3), (2 , 0), (2 , 1), (2 , 3)
不难看出,就是每个位置可能会有多个数产生对位的情况 , 我们还是延续刚才的容斥原理的想法往下推导:(以上面的为例)
总排列数显然是: A 4 2 A_{4}^{2} A42
至少有一个对位:
对位产生在0号位:0或者2 放在第0号位,其他3个随意排
对位产生在1号位:1或者3 放在第1号位,其他3个随意排
至少有两个对位:
对位同时产生在0,1号位上:有 2 × 2 2 \times 2 2×2 种情况
这个例子稍微简单一些,请自己分析更复杂的情况: n = 7 , m = 3 n = 7 , m = 3 n=7,m=3
分析清楚之后我们令第
i
i
i个花圃能够放的植物个数为
a
i
=
[
n
/
m
]
+
[
n
%
m
≤
i
]
a_i = [n / m] + [n \% m \leq i]
ai=[n/m]+[n%m≤i], 那么容斥的系数
b
i
b_i
bi为所有子集的乘积的和
b
i
=
∑
在
a
序列中取尽大小为
i
的子集
S
∏
j
∈
S
a
j
\Large b_i = \sum_{在a序列中取尽大小为i的子集S} \prod_{j \in S}^{} a_j
bi=在a序列中取尽大小为i的子集S∑j∈S∏aj
这个东西用动态规划求解:
令
d
p
i
,
j
dp_{i,j}
dpi,j 代表前
i
i
i个数,从中选出一个大小为j的子集的乘积的和。有转移
d
p
i
,
j
=
d
p
i
−
1
,
j
+
d
p
i
−
1
,
j
−
1
∗
a
i
dp_{i,j} = dp_{i-1,j} + dp_{i-1,j-1} * a_i
dpi,j=dpi−1,j+dpi−1,j−1∗ai
至此,总答案为:
a
n
s
=
A
n
m
−
d
p
m
,
1
A
n
−
1
m
−
1
+
d
p
m
,
1
A
n
−
1
m
−
1
−
.
.
.
+
(
−
1
)
m
d
p
m
,
m
A
n
−
m
0
ans = A_{n}^{m} - dp_{m,1}A_{n-1}^{m-1} + dp_{m,1}A_{n-1}^{m-1} - ... + (-1)^m dp_{m,m}A_{n-m}^{0}
ans=Anm−dpm,1An−1m−1+dpm,1An−1m−1−...+(−1)mdpm,mAn−m0
由于题目没有要求取模,则需要使用高精。
代码
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
public static BigInteger A(int n, int m) { //计算排列数
BigInteger res = new BigInteger("1");
for(int i = n ; i >= n-m+1 ; i --) { //A(n,m) = n*(n-1)*...*(n-(m-1))
res = res.multiply(new BigInteger(String.valueOf(i)));
}
return res;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int m = in.nextInt(), n = in.nextInt();
BigInteger[] a = new BigInteger[m + 5];
for (int i = 0; i < a.length; i++) { //初始化数组
a[i] = new BigInteger("0");
}
for (int i = 0 ; i < n ; i++){ //初始化每种植物的个数
a[i % m + 1] = a[i % m + 1].add(new BigInteger("1"));
}
BigInteger [][] dp = new BigInteger[m + 5][m + 5];
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) { //初始化dp数组
dp[i][j] = new BigInteger("0");
}
}
dp[0][0] = new BigInteger("1"); //初始化起始值
for (int i = 1 ; i <= m ; i++){
dp[i][0] = new BigInteger("1");
for (int j = 1 ; j <= i ; j++){
dp[i][j] = dp[i - 1][j - 1].multiply(a[i]).add(dp[i - 1][j]);
}
}
if(n < m) {
int t = n;
n = m;
m = t;
}
BigInteger ans = A(n, m);
for (int i = 1 ; i <= m ; i++){
BigInteger res = A(n-i, m-i).multiply(dp[m][i]);
if (i % 2 == 1) ans = ans.subtract(res);
else ans = ans.add(res);
}
System.out.println(ans);
}
}