[SDOI2019]移动金币

SDOI2019 移动金币

阶梯 Nim

阶梯Nim

有 n 堆石子, 标号为 1~n, 每次可以从第 i 堆中拿走一部分放到第 i-1 堆中, 或者把第 1 堆中的石子拿走一部分, 无法操作者算输。

结论: 等价于把所有奇数的位置拿出来, 进行普通 Nim 游戏。

普通 Nim 的异或和为 0 的计数

n 个石子放到 m 个容器, 使得所有容器的石子数的异或和为 0, 求方案数。

考虑每个容器的石子个数拆成二的幂的和, 然后按位考虑, 对于每一位 i 往偶数个容器里每一个放 2i 个石子。考虑 dp, dp(i,j) 表示从低到高放完前 i 位, 放了 j 个石子, 枚举这一位的 2i 的个数 k, 转移就是:

\[dp(i,j) = \sum_{k\equiv 0\mod 2} \binom mk dp(i-1,j-k2^i) \]


对于本题,转化成阶梯 Nim就是:把 n-m 个石子装进 m+1 个容器(标号为 1~m+1), 使得偶数标号的容器的石子数的异或和不为 0 的方案数。考虑直接统计异或和为 0 的方案数, 最后用总方案数减去。

#include <bits/stdc++.h>
typedef long long LL;
using namespace std;

const int N = 2e5 + 3, mo = 1e9 + 9; // prime

LL qpow (LL a, LL b) {
	a %= mo;
	LL res = 1ll;
	for (; b; b >>= 1, a = a * a % mo)
		if (b & 1) res = res * a % mo;
	return res;
}
LL fac[N], ifac[N];
LL C (int n, int m) {
	if (n < m) return 0ll;
	return fac[n] * ifac[m] % mo * ifac[n - m] % mo;
}
LL D (int n, int m) { // x1...xm = n + m - 1 m 
	return C (n + m - 1, m - 1);
}

void Inc (LL &x, LL y)  { x = (x + y) % mo; }

int b;
LL dp[21][N];
void gao (int n, int m) { // n stone -> m mol, naive Nim
	dp[0][0] = 1ll;
	for (b = 0; n >> b; ++ b) {
		for (int j = 0; j <= n; j += 2) {
			for (int k = 0; k <= m; k += 2) {
				if (j + (k << b) > n) break;
				Inc (dp[b + 1][j + (k << b)], dp[b][j] * C (m, k) % mo);
			}
		}
	}
}

int main()
{
	int n, m;
	cin >> n >> m;
		if (n < m) { putchar ('0'); return 0; }
	int l = n + m;
	fac[0] = 1ll;
	for (int i = 1; i <= l; ++ i) fac[i] = (LL)i * fac[i - 1] % mo;
	ifac[l] = qpow (fac[l], mo - 2);
	for (int i = l; i >= 1; -- i) ifac[i - 1] = (LL)i * ifac[i] % mo; 
	gao (n - m, (m + 1) / 2);
	n = n - m, m = m + 1;
	int h = m - m / 2;
	
	LL ans = 0ll;
	for (int i = 0; i <= n; ++ i) ans += dp[b][i] * D (n - i, h) % mo, ans %= mo;
	cout << (D (n, m) + mo - ans) % mo;
	return 0;
}
posted @ 2021-03-03 14:03  xwmwr  阅读(88)  评论(0编辑  收藏  举报