6

之前有人问过这个问题,但是没有一个得到明确的回答,我尝试编译我在这里找到的所有信息。如有必要,请随意合并/移动到另一个 stackexchange 站点。

以下是我发现的与此相关的问题:

该问题最初是作为 Interviewstreet Code Sprint 发布的,但现在它被列为实践问题。它也被移植到 SPOJ

这是问题陈述:

这是洗牌 N 张牌的算法:

1) 将牌分成 K 个相等的牌堆。

2)最底的N/K张牌同顺序属于1堆(所以初始堆的底牌就是1堆的底)。

3)从底部开始的下 N / K 张牌属于第 2 堆,依此类推。

4) 现在洗好的牌堆的顶牌是1堆的顶牌。下一张牌是2堆顶的牌,...,洗好的牌堆的第K张牌是K堆顶>牌。那么第(K + 1)张牌是现在在第 1 堆顶部的牌,第(K + 2)张是现在在第 2 堆顶部的牌,依此类推。

例如,如果 N = 6 且 K = 3,则一副牌“ABCDEF”(从上到下)洗牌一次时的顺序将变为“ECAFDB”。

给定 N 和 K,在堆恢复到其原始顺序之后需要的最少洗牌次数是多少?

输入:第一行包含测试用例 T 的数量。接下来的 T 行包含两个整数,每个整数 N 和 K。

输出:输出 T 行,每个测试用例包含所需的最小洗牌次数。如果套牌永远不会恢复到原来的顺序,则输出 -1。

约束:

  • K 将是 N 的一个因子。
  • T <= 10000
  • 2 <= K <= N <= 10^9

剧透警报 - 如果您想自己解决,请不要阅读下文。

问题可以翻译为:

找出需要执行 K 路(完美)洗牌的次数,以将一副 N 牌恢复到其初始顺序。

我采取了两种方法来解决这个问题。想到的第一个方法是:

  • 找到一个公式,给定初始顺序中的位置将生成卡片的下一个位置
  • 使用公式确定从第一堆(大小为 n / k)中的每张牌返回其初始位置所需的洗牌次数
  • 返回之前确定的随机数的最小公倍数

该解决方案的复杂度为 O(n / k + max_number_of_suhffles)。这是实际的实现。这样做的问题是它超过了最大时间,所以我开始寻找一个公式,可以让我在接近恒定的时间内得到这个数字。

我在这里可以优化的最多(例如,使用一些映射来缓存相同排列循环中的计算值等)是使其通过 interviewstreet 上的 3/10 测试。


我找到了这个实现,它假设返回初始状态所需的洗牌次数是K 相对于 N + 1的乘法顺序。来自 wiki:

As a consequence of Lagrange's theorem, ordn(a) always divides φ(n). 

φ(n) 是欧拉函数, ordn 是群阶——我们正在寻找的。我发现这篇论文使用 φ 来计算 shuffle 的数量,但它只适用于 2-way in-shuffle,而不是 k-way。

以下是此实施的步骤:

  • 预先计算出 < 100 000 的素数列表
  • φ(N+1)从它的主要因素计算。
  • φ(N + 1)通过以所有可能的方式组合其主要因素来确定所有的因素。
  • 依次尝试每个因素,得到最小的一个,x,验证k ^ x % N + 1 = 1

这个实现也发布在 GitHub 上

这运行得非常快,但自动评分器在 SPOJ 和 Interviewstreet 上的 10 次测试中有 9 次给了我一个“错误答案”分类。

我尝试比较两个实现的输出,但是对于我输入的测试用例(已知结果和随机),这两个实现总是输出相同的东西。这很奇怪,因为我很确定第一个算法是正确的,我认为第二个算法也应该是正确的。

“错误答案”分类可能来自代码中的运行时错误,但没有任何可能的原因跳出来。

我没有考虑到没有数字洗牌可以将牌组恢复到初始状态的情况——我的理解是这是不可能的。有限数量的完美洗牌最终将恢复初始排序,即使洗牌的数量可能非常高。

如果您花时间阅读本文,谢谢。:) 我很好奇这个问题,我想解决它。

4

3 回答 3

3

这是我在纸上进行了一些观察后得出的结论。

class CardShuffle {
    private long k;
    private long n;
    private long kn;
    private long kn2;

    public CardShuffle(long k, long n) {
            //I omitted some checks done here
        this.k = k;
        this.n = n;
        this.kn = k / n;
        this.kn2 = k - kn;
    }

    public long shuffle() {
        long count = 0L;
        long next = 0L;
        do {
               //this can be further optimized
           next = kn2 - kn * (next % n) + (next / n); 
           ++count;
        } while((next != 0L) && (count < k));
        if(count > k)
           return -1;
        return count;
    }
}

结果是...

Testing 1000000 : 2
#ms: 3.121905
#ms: 1424.487191
#1: 9900 #2: 9900
Testing 1000000 : 5
#ms: 1.409955
#ms: 556.329366
#1: 2475 #2: 2475
Testing 1000000 : 10
#ms: 0.007823
#ms: 186.797204
#1: 12 #2: 12
Testing 1000000 : 20
#ms: 0.590298
#ms: 275.751527
#1: 4950 #2: 4950
Testing 1000000 : 25
#ms: 0.298642
#ms: 260.559372
#1: 2475 #2: 2475
Testing 1000000 : 40
#ms: 1.187581
#ms: 241.956729
#1: 9900 #2: 9900
Testing 1000000 : 50
#ms: 1.187581
#ms: 241.015548
#1: 9900 #2: 9900
Testing 9999999 : 41841
#ms: 14.499887
#ms: 1829.868042
#1: 125000 #2: 125000
Testing 9999999 : 3333333
#ms: 58.119398
#ms: 311.005728
#1: 500000 #2: 500000
Testing 9999999 : 13947
#ms: 52.704185
#ms: 2095.336418
#1: 500000 #2: 500000

针对此输入进行测试...

10
1000000 2
1000000 5
1000000 10
1000000 20
1000000 25
1000000 40
1000000 50
9999999 41841
9999999 3333333
9999999 13947

首先#ms是以毫秒为单位的时间,我的方法,第二是你的。 #1#2分别是结果。

至于这个输入...

15
1000000000 2
1000000000 5
1000000000 10
1000000000 20
1000000000 25
1000000000 40
1000000000 50
1000000000 1000
1000000000 200000000
1000000000 250000000
1000000000 500000000
1000000000 50000000
999999999 1001001
999999999 37037037
999999999 333333333

我的方法在

Testing 1000000000 : 2
#ms: 71.360466
#1: 525780
Testing 1000000000 : 5
#ms: 68.987259
#1: 525780
Testing 1000000000 : 10
#ms: 0.008381
#1: 18
Testing 1000000000 : 20
#ms: 75.608492
#1: 525780
Testing 1000000000 : 25
#ms: 31.843154
#1: 262890
Testing 1000000000 : 40
#ms: 33.014531
#1: 262890
Testing 1000000000 : 50
#ms: 84.27384
#1: 525780
Testing 1000000000 : 1000
#ms: 0.006705
#1: 6
Testing 1000000000 : 200000000
#ms: 53.991778
#1: 525780
Testing 1000000000 : 250000000
#ms: 43.765898
#1: 262890
Testing 1000000000 : 500000000
#ms: 54.457201
#1: 525780
Testing 1000000000 : 50000000
#ms: 68.080999
#1: 525780
Testing 999999999 : 1001001
#ms: 115.060154
#1: 1000000
Testing 999999999 : 37037037
#ms: 5783.539528
#1: 50000000
Testing 999999999 : 333333333
#ms: 5391.880532
#1: 50000000

虽然您的第一个内存不足,但在我又旧又慢的笔记本电脑上。

我还没有验证这种方法,但在我看来它是有效的。您可以尝试查看它是否在某些输入上失败。我会很感激的。

如果您对我如何开发公式更感兴趣,请发表评论。

我也已经向interviewstreet提交了解决方案,但由于时间限制,它在第 4 次测试用例中失败了。

我很快就会尝试使用 ac 程序,并会在这里报告。

于 2012-08-22T13:20:03.057 回答
0

Given N and K, work out what permutation that produces, and work out what it looks like in http://en.wikipedia.org/wiki/Cycle_notation. In cycle notation, the permutation is a product of disjoint cycles, e.g. ABCDEF => ECAFDB is (AEDFBC) because A->E->D->F->B->C->A. If your permuation is a single cycle like this, the length of the cycle is the number of times you need to repeat it to get back to the same place. If the permuation is a product of multiple disjoint cycles such as (ABC)(DE)(F) the number of times you need to repeat is the http://en.wikipedia.org/wiki/Least_common_multiple of the lengths of the individual cycles.

于 2012-08-21T04:35:23.810 回答
-1
#include <iostream>
using namespace std;
int main() 
{
    int t, m , n, c, p, k, pos, cases;

    cin>>cases;

    while(cases--)
    {
        cin>>t;
        cin>>k;

        p = t/k;
        pos = 1;
        c = 0;

        do
        {
            c++;
            m = (pos - 1)%p + 1;
            n = k - (pos - m)/p;
            pos = k*(m-1) + n;

        }while(pos!=1);

        cout<<c<<endl;


    }
    return 0;
}
于 2012-09-20T14:22:48.420 回答