P2054 [AHOI2005] 洗牌

题目描述


为了表彰小联为 Samuel 星球的探险所做出的贡献,小联被邀请参加 Samuel 星球近距离载人探险活动。
由于 Samuel 星球相当遥远,科学家们要在飞船中度过相当长的一段时间,小联提议用扑克牌打发长途旅行中的无聊时间。玩了几局之后,大家觉得单纯玩扑克牌对于像他们这样的高智商人才来说太简单了。有人提出了扑克牌的一种新的玩法。
对于扑克牌的一次洗牌是这样定义的,将一叠 \(N\)(\(N\) 为偶数)张扑克牌平均分成上下两叠,取下面一叠的第一张作为新的一叠的第一张,然后取上面一叠的第一张作为新的一叠的第二张,再取下面一叠的第二张作为新的一叠的第三张……如此交替直到所有的牌取完。
如果对一叠 \(6\) 张的扑克牌 \({1,2,3,4,5,6}\),进行一次洗牌的过程如下图所示:

从图中可以看出经过一次洗牌,序列 \(1,2,3,4,5,6\) 变为 \(4,1,5,2,6,3\)。当然,再对得到的序列进行一次洗牌,又会变为 \(2,4,6,1,3,5\)。
游戏是这样的,如果给定长度为 \(N\) 的一叠扑克牌,并且牌面大小从 \(1\) 开始连续增加到 \(N\)(不考虑花色),对这样的一叠扑克牌,进行 \(M\) 次洗牌。最先说出经过洗牌后的扑克牌序列中第 \(L\) 张扑克牌的牌面大小是多少的科学家得胜。小联想赢取游戏的胜利,你能帮助他吗?

置换群


最初这道题目我的解题想法是,我们对最初的牌的下标视作一个模 \(N\)的简化剩余系 \(0,\cdots,N-1\)。那么此时的洗牌就是一个置换,设为 \(\sigma(L)\),而一次置换的效果就是:

0 --> 1
1 --> 3
2 --> 5
3 --> 0
4 --> 2
5 --> 4

我们可以从置换群的角度出发,将 \(\sigma(L)\)视作生成元,完成群内所有元素的闭合,最终确认 \(\sigma^M(L)\)的效果。

后面意识到如果我们要进行上述方法的实践,我们就需要对 \(N\)进行质因数分解从而确定 \(\sigma\)的阶,有些过于复杂了。

映射规律


如果我们将 \(0-indexed\)改为 \(1-indexed\),此时扑克牌的初始点数就可以与下标进行完美的对应了。于此同时:

  • 牌堆被分成了两堆,上半堆为 \(1,2,\cdots,{N\over2}\);下半堆为 \({N\over2}+1,\cdots,N\)。
  • 我们设洗牌一次之后, \(x\)位置的牌会被放在什么位置,设映射后的位置为 \(f(x)\):

    \[f(x)=\begin{cases}2x\quad\quad\quad\quad\quad\ \ \ ,x\leq{N\over2}\\2(x-{N\over2}) -1\quad ,x>{N\over2}\end{cases} \]

    把结果放在\(Zmod(N + 1)\)的意义下来看:

    \[f(x)=\begin{cases}2x\\2x-(N+1)\end{cases}\rightarrow f(x)\equiv\begin{cases}2x\pmod{N+1}\quad,x\leq{N\over2}\\2x\pmod{N+1}\quad,x>{N\over2}\end{cases} \]

    因此,不论是哪一张牌,其一次置换后的位置都是 \(f(x)\equiv 2x\pmod{N+1}\)。
  • 由同余的性质,我们可以断言,经过 \(M\)次洗牌(置换),最初的 \(x\)会被置换到 \(2^M\cdot x\pmod{N+1}\)的位置。题目给定的 \(L\)就是该结果,我们需要求解得到最初的 \(x\),即

    \[2^M\cdot x\equiv L\pmod{N+1}\xrightarrow{?}x \]

  • 按照原来求解阶的思路,我们需要求出 \(2\) 在模 \(N+1\) 下的阶 \(k\),就可以利用 \(M \bmod k\) 来化简指数。但其实我们只需要两边同乘 \(2^M\) 在模 \(N+1\) 下的乘法逆元即可。因为 \(N\) 是偶数,所以 \(N+1\) 必定是奇数。这意味着 \(\gcd(2, N+1) = 1\),\(2\) 在模 \(N+1\) 下的逆元必定存在:

    \[2\times {N+2\over2}=N+2\equiv1\pmod{N+1}\rightarrow 2^{-1}={N+2\over2}\pmod{N+1} \]

  • 现在我们的目标就转移到:

    \[2^M\cdot x\equiv L\pmod{N+1}\rightarrow x\equiv (2^M)^{-1}\cdot L\pmod{N+1}\equiv (2^{-1})^M\cdot L\pmod{N+1} \]

#include <iostream>

typedef __int128 ll128;

long long qpow(long long base, long long exp, long long mod) {
    long long res = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) res = (ll128)res * base % mod;
        base = (ll128)base * base % mod;
        exp >>= 1;
    }
    return res;
}

int main() {
    long long N, M, L;
    scanf("%lld %lld %lld", &N, &M, &L);
    long long inv2 = (N + 2) / 2;
    long long inv2M = qpow(inv2, M, N + 1);
    long long x = (ll128)inv2M * L % (N + 1);
    printf("%lld\n", x);
    return 0;
}
  • 时间复杂度:\(O(log\ M)\)
  • 空间复杂度:\(O(1)\)

最初想尝试用置换群的原因在于我并没有确认其中的”洗牌“的这个置换的规律,我认为它本身是没有规律的。之后用代数的形式看了一下:

  • 前半堆的每一张牌前面都会放置 \(2(x-1)+1\)张牌,其中\(2(x-1)\)是指自己前面的牌的都会有一张后半堆的牌与前半堆的牌进行两两配对,再加上本身所对应的那一张后半堆的牌。那么其映射就是 \(x\xrightarrow{f}2(x-1)+1+1=2x\);
  • 为了确定后半堆的牌是”后半堆的第几张牌“,我们执行 \(x-{N\over2}\)的操作,例如\(x={N\over2}+1\),它就是后半堆的第 \(1\)张牌。此后该数字就会称为其最终位置的一个参考点,我们会将其放在每隔两个位置的奇数位置上(因为中间会穿插一张前半堆的牌)。因此\(k=x-{N\over2}\),那么\(x\xrightarrow{f} 2k-1\)。

另外一个值得注意的点就是,题目中 \(N \le 10^{10}\),那么模数 \(N+1 \approx 10^{10}\)。在进行快速幂时,两数相乘会达到 \((10^{10})^2 = 10^{20}\)。而\(C++\)中 unsigned long long 的最大值约为 \(1.8 \times 10^{19}\),直接相乘可能会导致溢出。我们需要利用利用\(C++\)内置的\(128\)位整数类型 __int128 进行中转。

最后值得注意的一点是, \(2\)这个素数很特殊,其关于奇数的逆元一定是存在的,并且很容易进行求解。\(if\ N\ is\ odd\rightarrow 2 \times {N + 1\over2}\equiv 1\pmod{N}\)。

posted @ 2026-09-09 18:17  chen_xing  阅读(15)  评论(0)    收藏  举报