@bzoj - 4589@ Hard Nim


@description@

n 堆石子,每堆石子的数量是不超过 m 的一个质数。
两个人玩 nim 游戏,问使后手必胜的初始局面有多少种。
模 10^9 + 7。

input
多组数据。数据组数 <= 80。
每组数据一行两个正整数,n 和 m。1 <= n <= 10^9, 2 <= m <= 50000。
output
对于每组数据,输出一行答案。

sample input
3 7
4 13
sample output
6
120

@solution@

根据常识,异或起来等于 0,nim 游戏就一定后手必胜。

定义 dp(i, j) 前 i 堆石子异或起来等于 j 的方案数,则有转移:

\[dp(i, j) = \sum_{k\in S}dp(i-1, j\oplus k) \]

其中 S 是小于等于 m 的质数集合,初始情况 dp(0, 0) = 1。

这感觉起来像是一个卷积,但是又有点不像我们平常所见的卷积。
定义 \(f(i) = [i\in S]\),则 \(dp(i,j)=\sum f(k)*dp(i-1, j\oplus k)\)
好的它就是一个异或卷积。

我们的 dp(n)其实就是 f^n。因此,我们只需要先对 f 进行 FWT 正变换,再对每一个数进行快速幂,最后再 FWT 逆变换回来,f(0) 就是我们的答案。

@accepted code@

#include<cstdio>
const int MAXN = 100000;
const int MOD = int(1E9) + 7;
const int INV = (MOD + 1) >> 1;
int pow_mod(int b, int p) {
	int ret = 1;
	while( p ) {
		if( p & 1 ) ret = 1LL*ret*b%MOD;
		b = 1LL*b*b%MOD;
		p >>= 1;
	}
	return ret;
}
int f[MAXN + 5], prm[MAXN + 5], pcnt = 0;
bool isprm[MAXN + 5];
void init() {
	for(int i=2;i<=MAXN;i++) {
		if( !isprm[i] ) {
			prm[++pcnt] = i;
			f[i] = 1;
		}
		for(int j=1;1LL*i*prm[j]<=MAXN;j++) {
			isprm[i*prm[j]] = true;
			if( i % prm[j] == 0 )
				break;
		}
	}
}
void fwt(int *a, int n, int type) {
	for(int s=2;s<=n;s<<=1) {
		for(int i=0,t=(s>>1);i<n;i+=s) {
			for(int j=0;j<t;j++) {
				int x = a[i+j], y = a[i+j+t];
				a[i+j] = 1LL*(x + y)%MOD*(type == 1 ? 1 : INV)%MOD;
				a[i+j+t] = 1LL*(x + MOD - y)%MOD*(type == 1 ? 1 : INV)%MOD;
			}
		}
	}
}
int g[MAXN + 5];
int main() {
	init(); int n, m;
	while( scanf("%d%d", &n, &m) == 2 ) {
		int len = 1; while( len <= m ) len <<= 1;
		for(int i=0;i<len;i++)
			g[i] = 0;
		for(int i=1;i<=m;i++)
			g[i] = f[i];
		fwt(g, len, 1);
		for(int i=0;i<len;i++)
			g[i] = pow_mod(g[i], n);
		fwt(g, len, -1);
		printf("%d\n", g[0]);
	}
}

@details@

我才不会说我因为初始化只把m以内的数清零然后WA好几遍呢。

posted @ 2019-01-15 20:08  Tiw_Air_OAO  阅读(96)  评论(0编辑  收藏
把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end