【luogu P3214】卡农(数学)(DP)

卡农

题目链接:luogu P3214

题目大意

有 n 种元素,然后你要选 m 个互不相同的集合,满足里面有元素且在 n 种之间,每种至多一个,且每个元素在偶数个集合中出现过。
问你有多少种选法。

思路

考虑如果没有这些条件,只有互不相同。
那答案就是 \(A_{2^n-1}^m\)

考虑加上这个每个元素偶数个集合。
那你这个相当于每种元素是一维,然后要异或得到 \(0\)
不过因为你任选都可以(除了 \(0\)),所以只要前面 \(m-1\) 的结果不是 \(0\),你一定可以在 \(m\) 的位置调整得到 \(0\)
那前面的是 \(0\) 减去即可,那这不是递推吗:
\(f_m=A_{2^n-1}^{m-1}-f_{m-1}\)

但是问题是你最后调整的这个可能会跟前面的某个一样啊。
考虑再判掉,那考虑跟前面一样的消掉,那剩下的根据条件是 \(0\)
那你可以选择跟前面的哪个位置,那个位置是什么数,所以是:
\(f_m=A_{2^n-1}^{m-1}-f_{m-1}-f_{m-2}(m-1)(2^n-m+1)\)

然后 \(1e8+7\) 是质数放心弄就行。

代码

#include<cstdio>
#define ll long long
#define mo 100000007

using namespace std;

const int N = 1e6 + 100;
int n, m, f[N], jc[N], A[N];

int add(int x, int y) {return x + y >= mo ? x + y - mo : x + y;}
int dec(int x, int y) {return x < y ? x - y + mo : x - y;}
int mul(int x, int y) {return 1ll * x * y % mo;}
int ksm(int x, int y) {int re = 1; while (y) {if (y & 1) re = mul(re, x); x = mul(x, x); y >>= 1;} return re;}

int main() {
	scanf("%d %d", &n, &m);
	
	jc[0] = 1; for (int i = 1; i < N; i++) jc[i] = mul(jc[i - 1], i);
	int n2 = ksm(2, n); A[0] = 1; for (int i = 1; i <= m; i++) A[i] = mul(A[i - 1], dec(dec(n2, 1), i - 1));
	
	f[0] = 1; f[1] = 0;
	for (int i = 2; i <= m; i++) {
		f[i] = dec(A[i - 1], add(f[i - 1], mul(f[i - 2], mul(i - 1, dec(n2, i - 1)))));
	}
	
	printf("%d", mul(f[m], ksm(jc[m], mo - 2)));
	
	return 0;
}
posted @ 2022-10-20 15:48  あおいSakura  阅读(33)  评论(0)    收藏  举报