CF838C Future Failure

组合数奇偶判定的 trick。一句话总结就是 \(n - sum_n=k\)。其中 \(sum_n\) 表示 \(n\) 在二进制下的 \(1\) 的个数,\(k\) 表示 \(n!\) 含有因子 \(2\) 的数目。


首先如果当前串的不同排列数为偶数的话,也就是先手可以选择改变先后手的时候,先手必胜。

如果当前串的不同排列数为奇数:如果当前字符个数为奇数则先手必胜,否则先手必败。证明:设 \(n\) 为当前串长度,容易得到不同排列数为 \(p=\frac{n!}{\prod a_i!}\),其中 \(a_i\) 表示某种字符的个数。每次拿走一个字符相当于让 \(p\) 乘上一个 \(\frac{a_i}n\),那么我选取一个 \(a_i\) 使得 \(a_i!\) 含因子 \(2\) 的个数最少,容易发现这样选以后不会改变 \(p\) 的奇偶性,那么 \(p\) 始终为奇数,上述结论显然。

现在我们知道如何判断一个排列的胜负态了,准备考虑计数。根据最上面的那个 trick 我们知道 \(p\) 是奇数当且仅当 \(n - sum_n=\sum(a_i-sum_{a_i})\),其中 \(sum_x\) 表示 \(x\) 在二进制下 \(1\) 的个数。又因为 \(n=\sum a_i\),所以我们知道 \(sum_n=\sum sum_{a_i}\)。显然还必定有 \(n=a_1\bigotimes a_2 \bigotimes \cdots \bigotimes a_k\),其中 \(\bigotimes\) 表示异或操作,也就是 \(n\) 分解成二进制以后每个 \(1\) 被分到了一个且仅有一个 \(a_i\) 中。

然后就可以直接 DP 了:设 \(f_{i,s}\) 表示到第 \(i\) 个数,\(n\) 还剩 \(s\) 的方案数。转移非常显然,但是时间可能会超过,这里我们进行一个优化:强制每次必须选 \(lowbit(n)\)\(n\) 在二进制下最低位),然后就可以通过此题了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 250009, K = 250000;
int n, k, M, f[29][N], fac[N], inv_fac[N];

int ksm(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1)
			res = 1ll * res * a % M;
		b >>= 1, a = 1ll * a * a % M;
	}
	return res;
}

void init()
{
	scanf("%d %d %d", &n, &k, &M);
	fac[0] = 1;
	for (int i = 1; i <= K; i++)
		fac[i] = 1ll * fac[i - 1] * i % M;
	inv_fac[K] = ksm(fac[K], M - 2);
	for (int i = K - 1; i >= 0; i--)
		inv_fac[i] = 1ll * inv_fac[i + 1] * (i + 1) % M;
}

int lowbit(int x) { return x & (-x); }

int solve(int cur, int S)
{
	if (f[cur][S] != -1) return f[cur][S];
	if (!S)
	{
		f[cur][S] = fac[n];
		for (int i = 1; i <= cur; i++)
			f[cur][S] = 1ll * f[cur][S] * (k - i + 1) % M;
		return f[cur][S];
	}
	f[cur][S] = 0;
	int U = S - lowbit(S);
	for (int T = U; T; T = (T - 1) & U)
		f[cur][S] = (f[cur][S] + 1ll * inv_fac[S - T] * solve(cur + 1, T) % M) % M;
	f[cur][S] = (f[cur][S] + 1ll * inv_fac[S] * solve(cur + 1, 0) % M) % M;
	return f[cur][S];
}

void work()
{
	if (n & 1)
		printf("%d\n", ksm(k, n));
	else
	{
		memset(f, -1, sizeof(f));
		int res = (ksm(k ,n) - solve(0, n) + M) % M;
		printf("%d\n", res);
	}
}

int main()
{
	init();
	work();
	return 0;
}

posted @ 2020-11-01 13:44  With_penguin  阅读(393)  评论(0编辑  收藏  举报