七选五 排列组合+容斥/错排

8 篇文章 0 订阅
6 篇文章 0 订阅

好题,或许是因为我太菜了

题面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析
法一:容斥

不难想到用排列组合,问题就转化为:从 k k k 个空中选 x x x 个空,使它们全部填匹配的数字,而剩下的 k − x k-x kx 个空全都不匹配。
a n s = C ( x , k ) × . . . ans=C(x,k)\times... ans=C(x,k)×...
难点就在后面那里,怎么求出 k − x k-x kx 个空全都不匹配的方案数。
考虑 A ( k − x , n − x ) A(k-x,n-x) A(kx,nx) 为随便向这里面填数的方案数,显然这里面会有一些数恰巧匹配,所以我们需要减掉一些东西。
考虑 A ( k − x , n − x ) − C ( 1 , k − x ) × A ( k − x − 1 , n − x − 1 ) A(k-x,n-x)-C(1,k-x)\times A(k-x-1,n-x-1) A(kx,nx)C(1,kx)×A(kx1,nx1),限制了一个位置必须匹配,再减去,但这样会多减去重复的。
于是我们需要 容斥,考虑一个序列,枚举 a 1 a_1 a1 减了一次,枚举 a 2 a_2 a2 减了一次,所以需要在 a 1 , a 2 a_1,a_2 a1,a2 同时出现时加一次。同理 a 1 , a 2 a_1,a_2 a1,a2 a 2 , a 3 a_2,a_3 a2,a3 a 1 , a 3 a_1,a_3 a1,a3 同时出现时加了,需要在 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3 同时出现时减。
所以 a n s = C ( x , k ) × ( ∑ i = 0 k − x A ( k − x − i , n − x − i ) × C ( i , k − x ) × ( − 1 ) i ) ans=C(x,k)\times(\sum_{i=0}^{k-x}A(k-x-i,n-x-i)\times C(i,k-x)\times (-1)^i) ans=C(x,k)×(i=0kxA(kxi,nxi)×C(i,kx)×(1)i)
这里用个阶乘逆元,可以做到 O ( n ) \mathcal {O}(n) O(n)

Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5, MAXM = 105, Mod = 1e9 + 7;
int n, k, m, jc[MAXN], Inv[MAXN], c, d;
int Quick_Pow(int x, int y) {
	int ans = 1;
	for(; y; y >>= 1)  {
		if(y & 1) ans = ans * x % Mod;
		x = x * x % Mod;
	}
	return ans;
}
int C(int x, int y) { return jc[y] * Inv[x] % Mod * Inv[y - x] % Mod; }
int A(int x, int y) { return jc[y] * Inv[y - x] % Mod; }
signed main() {
	scanf("%lld%lld%lld", &n, &k, &m); jc[0] = 1;
	for(int i = 1; i <= n; i ++) jc[i] = jc[i - 1] * i % Mod;
	Inv[n] = Quick_Pow(jc[n], Mod - 2);
	for(int i = n - 1; i >= 0; i --) Inv[i] = Inv[i + 1] * (i + 1) % Mod;
	c = C(m, k);
	for(int i = 0; i <= k - m; i ++) {
		if(i & 1) d -= A(k - m - i, n - m - i) * C(i, k - m) % Mod, d %= Mod;
		else d += A(k - m - i, n - m - i) * C(i, k - m) % Mod, d %= Mod;
	}
	d = (d % Mod + Mod) % Mod; printf("%lld", c * d % Mod);
	return 0;
}
法二:错排

考虑错排一般的模型。给你 n n n 个数字,当且仅当 a i = i a_i=i ai=i 时,它是匹配的,要你寻找没有匹配的排列数量。
考虑 dp。
1.给 n n n 号位填数时,所填数字对应的位置|中的数字等于 n n n,即 n n n 与 另一个位置上的数交换了。
这时 d p [ n ] = d p [ n − 2 ] × ( n − 1 ) dp[n]=dp[n-2]\times (n-1) dp[n]=dp[n2]×(n1)
2.反之,一时半会也讲不清,我们的重点不是这个,直接给公式, d p [ n ] = d p [ n − 1 ] × ( n − 1 ) dp[n]=dp[n-1]\times (n-1) dp[n]=dp[n1]×(n1)
所以 d p [ n ] = ( n − 1 ) × ( d p [ n − 1 ] + d p [ n − 2 ] ) dp[n]=(n-1)\times (dp[n-1]+dp[n-2]) dp[n]=(n1)×(dp[n1]+dp[n2])

这道题和错排很像,发现在 n n n 中取了 x x x 个数必须合法以后,剩下的 k − x k-x kx 个位置就是错排的模型,而有 n − k n-k nk 个位置中的数可以随便选。
d p [ i ] [ j ] dp[i][j] dp[i][j] 为进行到第 i i i 个位置,选了 j j j 个不与任何位置匹配的数的方案数。
1.选不与任何位置匹配的数,令其为 a [ i d ] = v a l a[id]=val a[id]=val,这时,不与任何位置匹配的数肯定会少一个 ( v a l val val),但 i d id id 又成了新的不与任何位置匹配的数,所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] × j dp[i][j]=dp[i-1][j]\times j dp[i][j]=dp[i1][j]×j
2.选与某一位置匹配的数。考虑错排。
n n n 与另一个位置上的数交换(对应上面的第一点), d p [ i ] [ j ] = d p [ i − 2 ] [ j ] × ( i − 1 ) dp[i][j]=dp[i-2][j]\times (i-1) dp[i][j]=dp[i2][j]×(i1)
② 对应上面的第二点, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] × ( i − 1 ) dp[i][j]=dp[i-1][j]\times (i-1) dp[i][j]=dp[i1][j]×(i1)
发现第二维没动,所以 d p [ i ] = d p [ i − 1 ] × ( i + j − 1 ) + d p [ i − 2 ] × ( i − 1 ) dp[i]=dp[i-1]\times (i+j-1)+dp[i-2]\times(i-1) dp[i]=dp[i1]×(i+j1)+dp[i2]×(i1)。这里 j = n − k j=n-k j=nk

Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5, MAXM = 105, Mod = 1e9 + 7;
int n, k, m, jc[MAXN], Inv[MAXN], c, d, dp[MAXN];
int Quick_Pow(int x, int y) {
	int ans = 1;
	for(; y; y >>= 1)  {
		if(y & 1) ans = ans * x % Mod;
		x = x * x % Mod;
	}
	return ans;
}
int C(int x, int y) { return jc[y] * Inv[x] % Mod * Inv[y - x] % Mod; }
int A(int x, int y) { return jc[y] * Inv[y - x] % Mod; }
signed main() {
	scanf("%lld%lld%lld", &n, &k, &m); jc[0] = 1;
	for(int i = 1; i <= n; i ++) jc[i] = jc[i - 1] * i % Mod;
	Inv[n] = Quick_Pow(jc[n], Mod - 2);
	for(int i = n - 1; i >= 0; i --) Inv[i] = Inv[i + 1] * (i + 1) % Mod;
	c = C(m, k); dp[0] = 1;
	for(int i = 1; i <= k - m; i ++) dp[i] = dp[i - 1] * (i + n - k - 1) + (i >= 2 ? dp[i - 2] * (i - 1) : 0), dp[i] %= Mod;
	printf("%lld", c * dp[k - m] % Mod);
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值