プレゼント交換問題

id:Nabetani:20061217:p1 を解いてみる。言語は C。

  1. まず各参加者がどのグループからプレゼントをもらうか、と言う組み合わせを数える。
    1. 数えるだけ。記録しない。
  2. 乱数でその中から一つを選ぶ。
    1. 組み合わせを数えるときとほぼ同じ事を繰り返して(乱数で決めた数+1)番目のパターン。
  3. どの参加者からもらうか決める。

という方法(になっているはず)。
思ったこと

  • 変数や関数の名前がかっこ悪いのは気にしないでください。
  • 人数かグループ数が多くなると、計算に時間がかかる。
    • 15人が 5人、4人、3人、2人、1人のグループに分かれている場合、手元のパソコンで 0.4 秒程度。
    • 14人が 2人ずつ 7グループに分かれている場合、50〜100 秒程度。
    • 20人が 6人、5人、5人、4人のグループに分かれている場合、70〜150 秒程度。
    • 参加者 13人全員別のグループなんていうのだと組み合わせが 32bit int の範囲を越えるので、この方法ではきついだろう。
      • そういうのはランダムにもらう相手を決めて同じグループだったら計算し直し、のような方法がよさそう。
    • このアルゴリズムは人数が多いグループがあるときに速いっぽい。
  • 「どのグループからもらうか」の組み合わせが RAND_MAX に近くなると、"(int)(rand()/(RAND_MAX+1.0)*組み合わせ数)" の一様性が怪しく思える。
    • ちなみに「2人ずつ 7グループ」は組み合わせの数が RAND_MAX の 1/25 程度。

解答篇読んだ。努力の方向が間違っていた。こっぱずかしい。
以下ソース

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//人数
#define PERSON 15
//グループの数
#define GROUP 5
int counter;

int count_group_pattern(const int *group_list, const int *group_member_num);
void get_group_pattern(const int *group_list, int nth, int *present_from_g,
                       const int *group_member_num);
void get_pattern(const int *group_list, const int *present_from_g,
                 int *present_from_p, int *group_member_num);
void group_member_count(const int *group_list, int *group_member_num);

int main()
{
    //0...PERSON-1 の人がどのグループに属しているかのリスト
    int group_list[PERSON]={0,0,0,0,0,1,1,1,1,2,2,2,3,3,4};
    int present_from_g[PERSON], present_from_p[PERSON];
    int group_member_num[GROUP];
    int pattern_num, nth;
    int i;
    srand(time(NULL));

    group_member_count(group_list, group_member_num);
    pattern_num = count_group_pattern(group_list, group_member_num);
    if(pattern_num==0){
        puts("ありません");
        return 0;
    }
    //printf("%d\n", pattern_num);
    nth = (int)(rand()/(RAND_MAX+1.0)*pattern_num);
    get_group_pattern(group_list, nth, present_from_g, group_member_num);

    get_pattern(group_list, present_from_g, present_from_p, group_member_num);
    // get_pattern は group_member_num を破壊するので、
    // 繰り返したいときは group_member_count で値を計算し直すこと
    //group_member_count(group_list, group_member_num);
    for(i=0; i<PERSON; i++){
        printf("%c%d->%c%d ", present_from_g[i]+'a', present_from_p[i],
               group_list[i]+'a', i);
    }
    putchar('\n');
    return 0;
}

void group_member_count(const int *group_list, int *group_member_num)
{
    int i;
    for(i=0; i<GROUP; i++)
        group_member_num[i]=0;

    for(i=0; i<PERSON; i++){
        group_member_num[group_list[i]]++;
    }
    return;
}

int next_can_present(int point, int group,
                     const int *group_list, const int *present_from_g)
{
    while(point<PERSON &&
          (group_list[point]==group || present_from_g[point]>=0)){
        point++;
    }
    return point;
}

void count_group_pattern_r(const int *group_list, const int *group_member_num,
                           int *present_from_g,
                           int group, int g_member, int point)
{
    if(group>=GROUP) {
        counter++;
        return;
    }

    while((point=next_can_present(point, group, group_list, present_from_g))
          <PERSON){
        present_from_g[point]=group;

        if(g_member+1==group_member_num[group]){
            count_group_pattern_r(group_list, group_member_num, present_from_g,
                                  group+1, 0, 0);
        }else{
            count_group_pattern_r(group_list, group_member_num, present_from_g,
                                  group, g_member+1, point);
        }

        present_from_g[point]=-1;
        point++;
    }
    return;
}

int count_group_pattern(const int *group_list, const int *group_member_num)
{
    int present_from_g[PERSON];
    int i;
    counter=0;
    for(i=0; i<PERSON; i++)
        present_from_g[i]=-1;
    count_group_pattern_r(group_list,group_member_num,present_from_g,0,0,0);
    return counter;
}

void get_group_pattern_r(const int *group_list, int nth,
                         int *present_from_g, const int *group_member_num,
                         int group, int g_member, int point)
{
    if(group>=GROUP) {
        counter++;
        return;
    }

    while((point=next_can_present(point,group,group_list,present_from_g))
          <PERSON){
        present_from_g[point]=group;

        if(g_member+1==group_member_num[group]){
            get_group_pattern_r(group_list, nth, present_from_g,
                                group_member_num, group+1, 0, 0);
        }else{
            get_group_pattern_r(group_list, nth, present_from_g,
                                group_member_num, group, g_member+1, point);
        }
        if(nth<counter){
            break;
        }
        present_from_g[point]=-1;
        point++;
    }
    return;
}

void get_group_pattern(const int *group_list, int nth,
                       int *present_from_g, const int *group_member_num)
{
    int i;
    counter=0;
    for(i=0; i<PERSON; i++)
        present_from_g[i]=-1;
    get_group_pattern_r(group_list, nth, present_from_g,
                        group_member_num, 0, 0, 0);
    return;
}

void get_pattern(const int *group_list, const int *present_from_g,
                 int *present_from_p, int *group_member_num)
{
    int i, k, p, n, rem;

    for(i=0; i<PERSON; i++){
        present_from_p[i]=-1;
    }

    for(i=0; i<PERSON; i++){
        rem = group_member_num[group_list[i]];
        n=(int)(rand()/(RAND_MAX+1.0)*rem);
        k=0; p=-1;
        while(k<=n){
            p++;
            if(present_from_g[p]==group_list[i] && present_from_p[p]<0){
                k++;
            }
        }
        present_from_p[p]=i;
        group_member_num[group_list[i]]--;
    }
    return;
}

ちょっと修正

--- present.c.orig	Wed Dec 27 19:12:15 2006
+++ present.c	Wed Dec 27 19:18:46 2006
@@ -12,7 +12,7 @@
 void get_group_pattern(const int *group_list, int nth, int *present_from_g,
                        const int *group_member_num);
 void get_pattern(const int *group_list, const int *present_from_g,
-                 int *present_from_p, int *group_member_num);
+                 int *present_from_p);
 void group_member_count(const int *group_list, int *group_member_num);
 
 int main()
@@ -35,10 +35,7 @@
     nth = (int)(rand()/(RAND_MAX+1.0)*pattern_num);
     get_group_pattern(group_list, nth, present_from_g, group_member_num);
 
-    get_pattern(group_list, present_from_g, present_from_p, group_member_num);
-    // get_pattern は group_member_num を破壊するので、
-    // 繰り返したいときは group_member_count で値を計算し直すこと
-    //group_member_count(group_list, group_member_num);
+    get_pattern(group_list, present_from_g, present_from_p);
     for(i=0; i<PERSON; i++){
         printf("%c%d->%c%d ", present_from_g[i]+'a', present_from_p[i],
                group_list[i]+'a', i);
@@ -75,6 +72,10 @@
 {
     if(group>=GROUP) {
         counter++;
+        if(counter<=0){
+            fputs("Too many patterns\n",stderr);
+            exit(1);
+        }
         return;
     }
 
@@ -149,10 +150,12 @@
 }
 
 void get_pattern(const int *group_list, const int *present_from_g,
-                 int *present_from_p, int *group_member_num)
+                 int *present_from_p)
 {
     int i, k, p, n, rem;
+    int group_member_num[GROUP];
 
+    group_member_count(group_list, group_member_num);
     for(i=0; i<PERSON; i++){
         present_from_p[i]=-1;
     }