gmoj 6834. 2020.10.24【NOIP提高A组】T4.onmyodo

考场的时候没什么时间来想这道题。。。
通过打表找规律(题解),可以发现当(n)为奇数的时候先手必胜。

而对于(n)为偶数的情况。观察当前排列的方案数。
可以证明当当前排列的方案数为偶数的时候,先手必胜。
证明:若删字符可胜,便删字符;否则便重排,最后必定是后手不得不删字符(无法再重排)。

而对于方案数为奇数的情况的话,则可以证明删字符后的排列方案数仍为奇数,所以后手必胜。
综上,(n)为偶数时当前排列方案数为偶数的话先手才必胜,否则后手必胜。

考虑计算当前排列方案数为奇数的个数,观察(2)的个数。
显然,对于 (n!),质因数分解中(2)的次数为(sum_i leftlfloordfrac{n}{2^i} ight floor)

若排列有奇数种,则对任意(x)有:(sum_{i=1}^kleftlfloordfrac{a_i}{2^x} ight floor=leftlfloordfrac{n}{2^x} ight floor)

也就是对于二进制的每一位,要满足(sum a[i]_k = n_k)

于是我们可以用(DP)求解,设(f[i][j])表示当前用第(i)种字符,而当前(sum a[i])(j)的时候的方案数。

可以得到显然的转移:(f[i][j]=sum f[i - 1][l] / (j - l)!),其中(l)(j)的一个二进制子集。

由于求的是奇数中排列的方案数,答案便为(k^n-f[K][n]*n!)。当这样转移时间复杂度过高,会(TLE)

(force code)

#include <cstdio>
#define N 250010
#define mo 1000000009
#define ll long long
#define fo(x, a, b) for (int x = (a); x <= (b); x++)
#define fd(x, a, b) for (int x = (a); x >= (b); x--)
using namespace std;
int n, K;
ll jc[N], ny[N], f[27][N], ans = 1;

ll ksm(ll x, int y) {
	ll s = 1;
	while (y) {
		if (y & 1) s = s * x % mo;
		x = x * x % mo, y >>= 1;
	}
	return s;
}

void prepare() {
	jc[0] = ny[0] = 1;
	fo(i, 1, n) jc[i] = jc[i - 1] * i % mo;
	ny[n] = ksm(jc[n], mo - 2);
	fd(i, n - 1, 1) ny[i] = ny[i + 1] * (i + 1) % mo;
}

int main()
{
	freopen("onmyodo.in", "r", stdin);
	freopen("onmyodo.out", "w", stdout);
	scanf("%d%d", &n, &K);
	prepare(); ans = ksm(K, n);
	if (n & 1) return 0 & printf("%lld
", ans);
	f[0][0] = 1;
	fo(i, 1, K) fo(j, 0, n) {
		f[i][j] = f[i - 1][0] * ny[j] % mo;
		for (int k = j; k; k = (j & (k - 1)))
			f[i][j] = (f[i][j] + f[i - 1][k] * ny[j - k]) % mo;
	}
	printf("%lld
", (ans - jc[n] * f[K][n] % mo + mo) % mo);
	return 0;
}

所以我们要考虑优化一下(DP)
考虑转移的时候下一种字符强制包含剩下的(n')中的(1)的最低位。
这样我们的(i)就变成了用了(i)种字符了,而对于下一种字符是哪一个,并没有什么强制要求。
最后的奇数种排列的方案数就变成了(sum f[i][n]*k!/(k-i)!)

#include <cstdio>
#define N 250010
#define mo 1000000009
#define ll long long
#define fo(x, a, b) for (int x = (a); x <= (b); x++)
#define fd(x, a, b) for (int x = (a); x >= (b); x--)
using namespace std;
int n, K;
ll jc[N], ny[N], f[27][N], ans = 1, sum;

ll ksm(ll x, int y) {
	ll s = 1;
	while (y) {
		if (y & 1) s = s * x % mo;
		x = x * x % mo, y >>= 1;
	}
	return s;
}

void prepare(int tot) {
	jc[0] = ny[0] = 1;
	fo(i, 1, tot) jc[i] = jc[i - 1] * i % mo;
	ny[tot] = ksm(jc[tot], mo - 2);
	fd(i, tot - 1, 1) ny[i] = ny[i + 1] * (i + 1) % mo;
}

bool check(int x, int y) {
	if ((x & y) != y) return 1;
	int s = 1, mx = 0;
	while (x) {
		if (x & 1) mx = s;
		s <<= 1, x >>= 1;
	}
	if (mx != y) return 1;
	return 0;
}

int main()
{
	freopen("onmyodo.in", "r", stdin);
	freopen("onmyodo.out", "w", stdout);
	scanf("%d%d", &n, &K);
	prepare(n > K ? n : K); ans = ksm(K, n);
	if (n & 1) return 0 & printf("%lld
", ans);
	f[0][0] = 1;
	fo(i, 0, K - 1) fo(j, 0, n - 1) {
		if (! f[i][j] || (j & n) != j) continue;
		int rem = n ^ j, nx = rem & (-rem); rem = rem ^ nx;
		f[i + 1][j | nx] = (f[i + 1][j | nx] + f[i][j] * ny[nx]) % mo;
		for (int k = rem; k; k = (rem & (k - 1)))
			f[i + 1][j | k | nx] = (f[i + 1][j | k | nx] + f[i][j] * ny[k | nx]) % mo;
	}
	fo(i, 1, K) sum = (sum + f[i][n] * jc[K] % mo * ny[K - i] % mo) % mo;
	printf("%lld
", (ans - jc[n] * sum % mo + mo) % mo);
	return 0;
}
原文地址:https://www.cnblogs.com/jz929/p/13909457.html