あなたのkugyoを埋葬する

主に読書内容の整理のためのブログです。Amazon.co.jpアソシエイト。

最適配点問題

 以下はまたミクシィからの転載のブラッシュアップです。


 運動会で考えてもらうとわかりやすいかもしれませんが、例えば棒倒しは、1位をとった組には100点、2位の組には50点、3位の組には30点……というように、順位に応じて獲得点数が変わる競技です(正確な配分は忘れた)。そういう競技がいくつかあって、それらでの得点の総計が最終的な優勝組を決めるわけです。
 この仕組みは選挙でも同じことで、1人の有権者が1回の競技に相当します。つまり、有権者は候補を1位、2位……と評価したあとで、1位に1点、2位以下は0点を与えるわけです。逆に言えば、有権者Aさんという競技で1位をとった候補は1点を得て、2位以下の候補は0点を得る、というふうになっています。
 さて、選挙であれば(1,0,0,…)というある意味わかりやすい配分ですが、棒倒しの(100,50,30,…)という配分は、そうした理由が明確ではありません。理由があるとすれば、最終得点総計が1位になる組が2つ出ると、最終決戦が即決法(くじびき)かなんかになってしまうので、それを避けるためでしょう。
 さあそこで問題です。いったいどういう配分であれば、最終的に1位タイが発生する確率が、もっとも低くなるでしょうか?


 いま私が取り組んでいる問題はもう少し込み入っていて、5人が相互に投票しあうのですが、自分にだけは0点をつけなくてはならない、というものです。つまり、有権者は、自分以外の候補者4人について、独自の選好にしたがって1位〜4位の順位をつけ、点数を与えます。
 4位の点数は0でいいと思います。これは、たとえば(100,50,30,1)という配分と、(99,49,29,0)という配分では、タイ発生率は等しいだろうからです。また、1位の点もあまり高くしても意味がないでしょう。(5000,3,2,1)と(1000,3,2,1)とで差はなさそうです。では、1位の点をどこまで下げても差はでないのか? おそらくこのへんが突破口になりそうです。
 確率の問題は検算がしにくいので、チェックプログラムを用意しました。C言語で書いたつたないものです。これでも、自分以外の4人に重複しないように1〜4位の投票をする部分で、かなり苦労しました。考え付いてから、トランプのシャッフルと同じだ、と気づいて検索したところ、主流のものとだいたい合っていたようです。何かほかにプログラムミスがあったら教えてください。
 このプログラムにかけると、例えば(1,1,1,1)という配分では、タイ発生率は1です。(30,5,1,0)だと0.051909と出ましたが、100万回の試行ではまだ揺らぎが出るようで、またやったら0.052369でした。


 以下はチェックプログラム。検算機能などを入れたものを作って、必要な部分だけ抜いたものなので、むだが少しあるかもしれません。配列の名前については、work[]はちょっとへんかもしれません。これは最初に提示された問題が、上記のものともさらに違っていたためです。つまり、5人の人間が、それぞれの持ち寄った作品(work)について評価しあう、という問題だったのです。

#include<stdio.h> 
#include <stdlib.h> 
#include <time.h> 
void shuffle (int array[]){//各投票者の選好順を決める関数 
	int i; 
	int temp, target; 
	double z,R15=RAND_MAX+1; 
	for (i = 1; i <= 4; i++) {//i番目のカードに注目 
		z = rand() / R15; 
		target = (int)(z*4) + 1; //ここで左からtarget番目のカードを取得 
		temp = array[i];//i番目のカードを抜き取って・・・ 
		array[i] = array[target];//i番目の位置にtarget番目のカードを入れて・・・ 
		array[target] = temp;//target番目の位置に抜き取っていたカードを入れる 
	} 

} 

void vote (int agent, int array[], int work[]){//選好順に従い投票する関数 
	//work[agent]には0を入れ、ほかのwork[n]にはarray[n]を入れる 
	int n; 
	for(n=1; n<=4; n++){ 
		work[n] += array[n]; 
	}//work[4]までの投票を終えておく 
	if(agent == 5){ 
		work[5] += 0; 
	} 
	else{ 
		work[agent] -= array[agent];//加えなかったことに。つまり自分に入れた票を無効にして・・・ 
		work[5] += array[agent];//まだ何点にするか決めていなかったwork[5]に入れる 
	} 
} 

void sort(int a[]){//投票結果順に並べ替える関数 
	int k,j,i,exchanger; 
	for (k=1; k<=5; k++){ 
		for(j=k, i=k+1; i<=5; i++){ 
			if(a[i] < a[j]) j = i; 
		} 
		exchanger = a[k]; 
		a[k] = a[j]; 
		a[j] = exchanger; 
	} 
} 

void main (){ 
	int array[6],num,tie=0; 
	int agent,work[6],loop; 
	srand ((unsigned int)time (NULL)); 
	int loopmax=1000000;//ループ回数指定 
	for(num=1;num<=5;num++){ 
		array[num] = 0; 
	} 
	printf("得点配分を入力してください\n"); 
	for(num=1;num<=4;num++){ 
		printf("各エージェントは%d位だと思うものに何点入れますか...",num); 
		scanf("%d",&array[num]); 
	} 
	for(num=1;num<=5;num++){ 
		work[num] = 0;//1回の投票中に結果を記憶する配列 
	} 

	for(loop=1;loop<=loopmax;loop++){//loopmax回の投票と集計に入る 
		for (agent=1;agent<=5;agent++){ 
			shuffle(array); 
			vote(agent, array, work); 
		} 
		//ここまでで投票が1回終わり 
		sort(work); 
		if(work[5] == work[4]){ 
			tie++;//1位と2位が同じだったらtieを増やす 
		} 
		for (num=1;num<=5;num++){//次の投票へ向けworkをカラに 
			work[num] = 0; 
		} 
	} 
	printf("\nタイの発生率は%f\n",(double)tie/loopmax); 
}