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

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

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

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

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

若排列有奇数种,则对任意\(x\)有:\(\sum_{i=1}^k\left\lfloor\dfrac{a_i}{2^x}\right\rfloor=\left\lfloor\dfrac{n}{2^x}\right\rfloor\)

也就是对于二进制的每一位,要满足\(\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\n", 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\n", (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\n", 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\n", (ans - jc[n] * sum % mo + mo) % mo);
	return 0;
}
posted @ 2020-11-01 14:37  jz929  阅读(106)  评论(0编辑  收藏  举报