ココロコネクト・ヒトランダムの問題

こんな問題

を考えた。

とりあえず思いつく解法としては、それぞれの状態になる確率を記憶しておいての動的計画法

//  順列を一意な整数に変換
int perm2id(vector<int>v)
{
    int N=(int)v.size();
    int r=0;
    for (int i=0;i<N;i++){
        int t=v[i];
        for(int j=0;j<i;j++)if(v[j]<v[i])t--;
        r=r*(N-i)+t;}
    return r;
}

//  一意な整数から順列に変換
vector<int>id2perm(int N,int r)
{
    vector<int>v(N);
    for(int i=N-1;i>=0;i--)v[i]=r%(N-i),r/=N-i;
    vector<bool>f(N);
    for(int i=0;i<N;i++){
        int t=0;
        while(f[t])t++;
        for(int j=0;j<v[i];j++){t++;while(f[t])t++;}
        v[i]=t;f[t]=true;}
    return v;
}

double solve( int N, int k )
{
    int L = 1;
    for ( int i=1; i<=N; i++ )
        L *= i;

    vector<double> T(L);
    T[0] = 1;

    for ( int i=0; i<k; i++ )
    {
        vector<double> P = T;
        T = vector<double>(L);

        for ( int j=0; j<L; j++ )
        {
            vector<int> v = id2perm(N,j);
            for ( int a=0; a<N; a++ )
            for ( int b=0; b<a; b++ )
            {
                swap(v[a],v[b]);
                T[perm2id(v)] += P[j]/(N*(N-1)/2);
                swap(v[a],v[b]);
            }
        }
    }

    return T[0];
}

計算量(kN3N!)で、N=10くらいが限界。

N   k   prob           time[s]
--- --- -------------- ------
  2  10 1.000000000000   0.00
  3  10 0.333333333333   0.00
  4  10 0.083346034649   0.00
  5  10 0.016927126000   0.00
  6  10 0.003201527728   0.02
  7  10 0.000662616042   0.27
  8  10 0.000159123443   3.28
  9  10 0.000044105344  44.39
 10  10 0.000013859489 640.31

N=10で640秒だから、N=11なんて計算すると、おねえさん死んじゃう!やめて!

指数的であることに変わりはないけど、もう少し速い方法。部員の置換を相異なる巡回置換に分解したときの、巡回置換の大きさがそれぞれ等しければ、その状態になる確率は等しい。例えば、

1→2→3→1 4→4 5→6→5

という入れ替わりと、

1→1 2→6→3→2 4→6→4

という入れ替わりが発生する確率は等しい。整数Nを順番の違いを除いて和で表す方法の総数(分割数)は階乗に比べると小さく、N=42でも37338にしかならない。これを使うとN=40くらいまで計算できる。

//  Nの分割を一意な整数に変換・逆変換
class Part
{
    vector<vector<int> >P;
    void f(int N,int k,vector<int>&v){
        if(N==0)P.push_back(v);
        for(int i=k;i<=N;i++){
            v.push_back(i);
            f(N-i,i,v);
            v.pop_back();}}
public:
    Part(int N){
        vector<int>v;
        f(N,1,v);}
    int size()const{
        return(int)P.size();}
    int part2id(vector<int>v)const{
        int r=0,b=1;
        while(b<(int)P.size())b*=2;
        for(;b>0;b/=2)
            if(r+b<(int)P.size()&&P[r+b]<=v)
                r+=b;
        return r;}
    vector<int>id2part(int r)const{
        return P[r];}
};

double solve( int N, int k )
{
    Part Pt(N);

    vector<double> T(Pt.size());
    T[0] = 1;

    for ( int i=0; i<k; i++ )
    {
        vector<double> P = T;
        T = vector<double>(Pt.size());

        for ( int j=0; j<Pt.size(); j++ )
        {
            vector<int> p = Pt.id2part(j);

            //  異なる巡回置換に属する要素を交換する場合
            for ( int a=0; a<(int)p.size(); a++ )
            for ( int b=0; b<a; b++ )
            {
                vector<int> q = p;
                q.erase(q.begin()+a);
                q.erase(q.begin()+b);
                q.push_back(p[a]+p[b]);
                sort(q.begin(),q.end());
                T[Pt.part2id(q)] += P[j]*p[a]*p[b]/(N*(N-1)/2);
            }

            //  同じ巡回置換に属する要素を入れ替える場合
            for ( int a=0; a<(int)p.size(); a++ )
            for ( int k=1; k<p[a]; k++ )
            {
                vector<int> q = p;
                q.erase(q.begin()+a);
                q.push_back(k);
                q.push_back(p[a]-k);
                sort(q.begin(),q.end());
                T[Pt.part2id(q)] += P[j]*p[a]/2/(N*(N-1)/2);
            }
        }
    }

    return T[0];
}

実行結果。

N   k   prob           time[s]
--- --- -------------- ------
  2  10 1.000000000000   0.00
  3  10 0.333333333333   0.00
  4  10 0.083346034649   0.00
  5  10 0.016927126000   0.00
  6  10 0.003201527728   0.00
  7  10 0.000662616042   0.00
  8  10 0.000159123443   0.00
  9  10 0.000044105344   0.00
 10  10 0.000013859489   0.00
 11  10 0.000004847868   0.00
 12  10 0.000001857739   0.01
 13  10 0.000000769656   0.01
 14  10 0.000000341006   0.02
 15  10 0.000000160139   0.02
 16  10 0.000000079123   0.04
 17  10 0.000000040881   0.05
 18  10 0.000000021974   0.07
 19  10 0.000000012235   0.10
 20  10 0.000000007031   0.14
 21  10 0.000000004157   0.21
 22  10 0.000000002522   0.27
 23  10 0.000000001566   0.37
 24  10 0.000000000993   0.53
 25  10 0.000000000642   0.71
 26  10 0.000000000423   0.91
 27  10 0.000000000283   1.23
 28  10 0.000000000193   1.83
 29  10 0.000000000133   2.13
 30  10 0.000000000093   2.79
 31  10 0.000000000066   4.81
 32  10 0.000000000047   5.67
 33  10 0.000000000034   6.18
 34  10 0.000000000025   7.87
 35  10 0.000000000018  10.29
 36  10 0.000000000014  12.98
 37  10 0.000000000010  16.43
 38  10 0.000000000008  30.46
 39  10 0.000000000006  26.77
 40  10 0.000000000005  34.07

ちなみに、kをもう少し大きくするとこんな感じで、2/N!になる。

N   k   prob           time[s]
--- --- -------------- ------
  2 100 1.000000000000   0.00
  3 100 0.333333333333   0.00
  4 100 0.083333333333   0.00
  5 100 0.016666666667   0.00
  6 100 0.002777777778   0.00
  7 100 0.000396825397   0.01
  8 100 0.000049603175   0.01
  9 100 0.000005511464   0.02
 10 100 0.000000551146   0.03

kが奇数なら、0。偶置換の個数がN!/2で、それぞれが等確率で発生するということなのだと思う。