洛谷P2054 [AHOI2005]洗牌(扩展欧几里德)

洛谷题目传送门

来个正常的有证明的题解

我们不好来表示某时刻某一个位置是哪一张牌,但我们可以表示某时刻某一张牌在哪个位置。

设数列\(\{a_{i_j}\}\)表示\(i\)号牌经过\(j\)次洗牌后的位置,我们试着来递推一下

首先,如果此刻牌在上面一叠,显然\(a_{i_{j+1}}=2a_{i_j}\)

接着,如果这张牌在下面一叠,那么\(a_{i_{j+1}}=2(a_{i_j}-\frac n2)-1=2a_{i_j}-(n+1)\),应该也很好推出来

写在一起,观察一下

\[a_{i_{j+1}}=\begin{cases}2a_{i_j}\qquad\qquad\ ,a_{i_j}\leq\frac n2\\2a_{i_j}-(n+1),a_{i_j}>\frac n2\end{cases} \]

诶,两个式子都有一个系数\(2\)呢!那我们可不可以把它看成模\(n+1\)意义下的结果呢?

于是可以进一步得到\(a_{i_j}\equiv2^mi(\mod n+1)\)\(i\)就是\(a_{i_0}\)

题目已经知道了\(a_{i_j}\),来求\(i\),不就是一个线性同余不定方程么?exgcd搞一下就好啦!因为\(\gcd(2^m,n+1)=1\),所以根本不用像青蛙的约会那样麻烦。

看楼上大佬用异或写swap?!第一次见的蒟蒻表示只能Orz。然后蒟蒻就把exgcd这个函数成功压缩到了一行。。。。。。

#include<cstdio>
#define LL long long
LL n,m,l,x=1,y;
LL qpow(LL b,LL k){//快速幂求2^m
	LL a=1;
	while(k){
		if(k&1)a*=b,a%=n+1;
		b*=b,b%=n+1;
		k>>=1;
	}
	return a;
}
void exgcd(LL a,LL b){//一行exgcd233
	if(b)exgcd(b,a%b),(y^=x^=y^=x)-=a/b*x;
}
int main(){
	scanf("%lld%lld%lld",&n,&m,&l);
	exgcd(qpow(2,m),n+1);
	printf("%lld\n",(l*x%(n+1)+n+1)%(n+1));//注意化成最小正整数
	return 0;
}
posted @ 2018-06-07 21:35  Flash_Hu  阅读(224)  评论(0编辑  收藏  举报