题目概述

本题要求模拟一个洗牌机的运行过程。洗牌机有 NNN 个槽位,初始时第 iii 张牌放在槽位 iii 。洗牌机有一个固定的洗牌函数 fff ,它表示在一次洗牌轮次后,原本在槽位 iii 的牌会被移动到槽位 f(i)f(i)f(i) 。现在给定 NNN 、洗牌轮数 RRR 以及洗牌函数 fff (以排列形式给出),要求输出初始时在槽位 iii 的牌在经过 RRR 轮洗牌后所在的槽位。

输入范围

  • NNN 最多为 104010401040
  • RRR0≤R<2630 \leq R < 2^{63}0R<263 ,是一个非常大的整数

样例输入

5 0
4 2 1 5 3
5 1
4 2 1 5 3
5 2
4 2 1 5 3

样例输出

1 2 3 4 5
4 2 1 5 3
5 2 4 3 1

解题思路分析

1. 问题本质

这个问题本质上是在计算排列的 RRR 次幂。给定一个排列 fff ,我们需要对每个 iii1≤i≤N1 \leq i \leq N1iN )求出 fR(i)f^R(i)fR(i) ,即 fff 连续作用 RRR 次后的结果。

2. 直接模拟的不可行性

由于 RRR 最大可达 263−12^{63}-12631 ,直接模拟 RRR 次操作显然会超时。我们需要寻找更高效的方法。

3. 利用排列的循环分解性质

任何一个排列都可以唯一分解为若干个不相交的循环(环)。例如,对于排列 f=[4,2,1,5,3]f = [4, 2, 1, 5, 3]f=[4,2,1,5,3] ,我们可以分解为两个环:

  • C1C_1C11→4→5→3→11 \rightarrow 4 \rightarrow 5 \rightarrow 3 \rightarrow 114531 (长度为 4)
  • C2C_2C22→22 \rightarrow 222 (长度为 1)

关键观察 :在同一个环中,经过 RRR 次置换等价于在环中向前移动 RRR 步。由于环是循环的,移动 RRR 步等价于移动 R mod LR \bmod LRmodL 步,其中 LLL 是该环的长度。

4. 算法步骤

  1. 环分解 :遍历 111NNN ,找出所有环,并为每个位置记录:
    • 它属于哪个环(环编号)
    • 它在环中的位置(索引,从 000 开始)
    • 环的长度
  2. 计算结果 :对于每个位置 iii
    • 找到它所在的环 CCC ,环长为 LLL
    • 设它在环中的索引为 ppp
    • 则经过 RRR 轮后,新索引为 (p+R) mod L(p + R) \bmod L(p+R)modL
    • 从环 CCC 中取出新索引对应的元素,即为 fR(i)f^R(i)fR(i)
  3. 特判 :如果 R=0R = 0R=0 ,则输出原始排列 1,2,…,N1, 2, \dots, N1,2,,N

5. 时间复杂度

  • 环分解: O(N)O(N)O(N)
  • 计算结果: O(N)O(N)O(N)
  • 总复杂度: O(N)O(N)O(N) ,可以轻松处理 N≤104N \leq 10^4N104 的数据规模

6. 溢出问题注意

RRR 可能非常大( <263< 2^{63}<263 ),直接计算 (p+R)(p + R)(p+R) 可能溢出 646464 位整数。但由于我们只需要 R mod LR \bmod LRmodL ,而 L≤N≤1040L \leq N \leq 1040LN1040 ,因此可以先对 RRR 取模 LLL ,再用取模后的值进行计算,避免溢出。


代码实现

// Shuffling Cards
// UVa ID: 12642
// Verdict: Accepted
// Submission Date: 2026-01-13
// UVa Run Time: 0.100s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 1200;

int n;
int perm[MAXN];        // 原始置换 f
int cur[MAXN];         // 当前置换
int tmp[MAXN];         // 临时数组
int cyclePos[MAXN];    // 每个位置在所属环中的序号(从0开始)
int cycleId[MAXN];     // 每个位置所属环的编号
vector<int> cycles[MAXN]; // 存储每个环的元素
bool visited[MAXN];

// 找出所有环并存储
void decomposeCycles() {
    memset(visited, false, sizeof visited);
    int cid = 0;
    for (int i = 1; i <= n; i++) {
        if (!visited[i]) {
            int cur = i;
            while (!visited[cur]) {
                visited[cur] = true;
                cycleId[cur] = cid;
                cyclePos[cur] = cycles[cid].size();
                cycles[cid].push_back(cur);
                cur = perm[cur];
            }
            cid++;
        }
    }
}

int main() {
    ll r;
    while (cin >> n >> r) {
        for (int i = 1; i <= n; i++) cin >> perm[i];
        // 如果 r=0,置换为单位置换
        if (r == 0) {
            for (int i = 1; i <= n; i++) {
                if (i > 1) cout << ' ';
                cout << i;
            }
            cout << '\n';
            continue;
        }
        // 初始化环数组
        for (int i = 0; i < n; i++) cycles[i].clear();
        decomposeCycles();
        // 计算最终位置
        for (int i = 1; i <= n; i++) {
            int cid = cycleId[i];
            int pos = cyclePos[i];
            ll cycleLength = cycles[cid].size();
            int rr = r % cycleLength;
            // 在环中移动 r 步
            int newPos = (pos + rr) % cycleLength;
            int target = cycles[cid][newPos];
            cur[i] = target;
        }
        // 输出结果
        for (int i = 1; i <= n; i++) {
            if (i > 1) cout << ' ';
            cout << cur[i];
        }
        cout << '\n';
    }
    return 0;
}

总结

本题的关键在于利用排列的循环分解性质,将 RRR 次置换转化为在环中的循环移动。通过取模操作,我们避免了直接模拟 RRR 次操作带来的时间复杂度和溢出风险。这种思想在处理排列幂运算、置换群相关问题中非常常见,是一类经典技巧。

对于 N≤1040N \leq 1040N1040R<263R < 2^{63}R<263 的输入范围,本算法能够在 O(N)O(N)O(N) 时间内高效求解,完全满足题目要求。

更多推荐