1200 石子游戏 V2
题目来源: TopCoder
基准时间限制:4 秒 空间限制:131072 KB 分值: 640 难度:8级算法题 收藏 关注
Nim游戏的规则如下:A B两个人轮流拿石子,A先拿。每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。不同的初始局面,决定了最终的获胜者,有些局面下先拿的会赢,其余的先手会负。
共有N堆石子,每堆石子的数量都是 <= M的质数。求符合条件的先手负的局面有多少种。例如: N = 3(共3堆石子),M = 7,小于M的质数为:2, 3, 5, 7。如果初始局面为(2,5,7)则先手负。同理,(5,7,2)以及其它(2,5,7)的排列,都符合先手负的条件,并且除此之外的其他组合都不能做到先手负。所以符合条件的局面共有3! = 6种。由于符合条件的局面很多,输出Mod 10^9 + 7的结果即可。
注:2 2 3 3同 2 3 2 3 算2种不同的局面。
Input
输入共2个数:N, M中间用空格分隔。N为石子的堆数,M为每堆石子数量的上限,并且每堆石子的数量为质数。(2 <= N <= 10^9,2 <= M <= 50000)。
Output
输出符合条件的局面的数量Mod 10^9 + 7。
Input示例
10 100
Output示例
294844622
【分析】
朴素的想法是做n次DP
然后做n次就炸了。
用到快速我二婶变换,针对异或版本的。
算是个裸题吧…
【代码】
//bzoj 4589 Hard Nim
#include<bits/stdc++.h>
#define N 50000
#define ll long long
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=50005;
const int mod=1e9+7;
bool vis[mxn];
int n,m,T,inv,L;
int pri[mxn],a[mxn<<1];
inline int ksm(int x,int k)
{
if(k==1) return x;
int tmp=ksm(x,k>>1);
if(k&1) return (ll)tmp*tmp%mod*x%mod;
else return (ll)tmp*tmp%mod;
}
inline void init()
{
int i,j;
inv=ksm(2,mod-2);
fo(i,2,N)
{
if(!vis[i]) pri[++pri[0]]=i;
for(j=1;j<=pri[0] && (ll)i*pri[j]<=N;j++)
{
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
}
inline void DWT(int l,int r) //fan
{
if(l==r) return;
int mid=l+r>>1;
for(int i=l,j=mid+1;i<=mid;i++,j++)
{
int x=(ll)(a[j]-a[i]+mod)*inv%mod;
a[i]=(ll)(a[i]+a[j])*inv%mod;
a[j]=x;
}
DWT(l,mid),DWT(mid+1,r);
}
inline void FWT(int l,int r) //zheng
{
if(l==r) return;
int mid=l+r>>1;
FWT(l,mid),FWT(mid+1,r);
for(int i=l,j=mid+1;i<=mid;i++,j++)
{
int x=(a[i]+a[j])%mod;
a[i]=(a[i]-a[j]+mod)%mod;
a[j]=x;
}
}
int main()
{
init();
int i,j;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(a,0,sizeof a);
fo(i,1,pri[0]) if(pri[i]<=m) a[pri[i]]=1;
L=1;while(L<=m) L<<=1;L--;
FWT(0,L);
fo(i,0,L) a[i]=ksm(a[i],n);
DWT(0,L);
printf("%d\n",a[0]);
}
return 0;
}