BZOJ1965 [Ahoi2005]SHUFFLE 洗牌 快速幂

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - BZOJ1965


题意概括

  对于扑克牌的一次洗牌是这样定义的,将一叠N(N为偶数)张扑克牌平均分成上下两叠,取下面一叠的第一张作为新的一叠的第一张,然后取上面一叠的第一张作为新的一叠的第二张,再取下面一叠的第二张作为新的一叠的第三张……如此交替直到所有的牌取完。

  经过一次洗牌,序列1 2 3 4 5 6变为4 1 5 2 6 3。当然,再对得到的序列进行一次洗牌,又会变为2 4 6 1 3 5。 游戏是这样的,如果给定长度为N的一叠扑克牌,并且牌面大小从1开始连续增加到N(不考虑花色),对这样的一叠扑克牌,进行M次洗牌。

  扑克牌序列中第L张扑克牌的牌面大小是多少?


题解

  我们发现,一次操作其实就是把第i个位置的牌放到第(2*i) mod (n+1)个位置。

  于是我们可以列出方程:

  设答案为x,

  x • 2m Ξ L (mod (n + 1))

  mod (n+1)条件下,2 的逆元是 n/2+1

  故可以移项,得:

  x = (n / 2 + 1)m • L mod (n + 1)

  于是快速幂跑一跑就可以了。

  但是会有中间乘法溢出的情况。

  一位一位乘就可以了。


代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
typedef long long LL;
LL n,m,L,mod;
LL times(LL a,LL b){
	LL ans=0;
	for (int i=40;i>=0;i--){
		ans=(ans<<1)%mod;
		if ((b>>i)&1)
			ans=(ans+a)%mod;
	}
	return ans;
}
LL Pow(LL a,LL b){
	LL ans=1,now=a;
	while (b){
		if (b&1)
			ans=times(ans,now);
		now=times(now,now);
		b>>=1;
	}
	return ans;
}
int main(){
	scanf("%lld%lld%lld",&n,&m,&L);
	mod=n+1;
	printf("%lld",times(Pow(n/2+1,m),L));
	return 0;
}

  

posted @ 2017-10-28 15:07  zzd233  阅读(211)  评论(0编辑  收藏  举报