1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「隣のトイレを使わないでっ!」 最大で何人が同時にトイレを使えるか考えてみよう

Posted at

皆さんはトイレを使用するときに、既に利用する人がすぐ隣にいるトイレを使用するのを避け、何個か空けてトイレを利用することはありませんか? そんなことから着想を得て思いついた次の問題について、本記事では考えてみます。

この記事で扱う問題

この建物には$N$個のトイレがあります。しかしこの街の住人は恥ずかしがりやで、既にトイレを利用している人がいる場合には、他の人から最も遠いトイレを選んで利用します。また他の人が既に利用している隣のトイレを利用することはできません。最初の人が最適なトイレを選んだ場合に、最大で何人のユーザが同時にトイレを利用することができるでしょうか。

経緯

私は男子トイレを利用しますが、小便器と個室のどちらを利用する場合でも、隣に人がくると少し緊張しています。また、私からはあまり人が利用している隣のトイレを選ぶことはありません。このように感じる方は意外と多いのではないでしょうか。どうやら海外の方でも同じように感じる方もいるようです。

そこでこの行動を次のようなルールに落とし込んだ問題について、検討することにします。

  • 人が利用しているトイレがある場合には、他の人から最も遠いトイレを利用する
  • 人が使用している隣のトイレしか空いていない場合には、トイレの利用をあきらめる

このようなルールを設定した場合に、最大で何人のユーザが使用できるのでしょうか。
また、どのような傾向があるのかを検討していきます。これを競技プログラミング風の問題文にしたのが、記事の先頭に記載した問題です。

具体的な例

例えば次の図のような$5$個のトイレがある例を考えてみます。
5_sample-図 5p2.drawio.png

この例では、最初の$1$人が左から$2$番目のトイレを利用しました。次に$2$人目が最も離れた右側のトイレを利用します。そうすると、残りの$3$つの空きトイレはいずれも利用中のユーザの隣になるため、利用することができません。

しかし最初の$1$人が別の場所を選ぶと、異なる結果となります。最初の$1$人が最も左のトイレを選んだとします。

5_sample-図 5p3.drawio.png

この例では、$2$人目が最も右のトイレを選び、$3$人目が真ん中のトイレを利用できます。このように最初の$1$人が選んだ場所により、利用できるユーザの数が変わってくることがわかります。この記事では 最初のユーザが選んだトイレをFirst Position(FP)と呼ぶことにします。 しかし最初のユーザが端のトイレを選べば必ずしも良いわけではなく、次のような例ではうまくいきません。

5_sample-図 7p3.drawio.png

Step$3$で$3$人目のユーザが真ん中のトイレを利用しますが、この場合は4つのデッドスペースが生じてしまいます。

$7$個のトイレがある場合には、例えば左から$3$番目のトイレから使い始めると、うまくいきます。
5_sample-図 7p4.drawio.png

トイレが奇数個で、すべての奇数番目のトイレが埋まった場合には、利用率が$0.5$を超えます。また偶数個のトイレでは利用できるトイレがすべて埋まった場合には、利用率が$0.5$になります。

簡単な実装

まずは簡単なプログラムを作成し、$N$個のトイレと最初のユーザが使うトイレの番号(左端が$1$、右端が$N$となります)を指定します。どのようにトイレが埋まっていくかを可視化するプログラムです。

━━━━━━━━━━━━━━━━━━╮
┃    ソースコードを表示(折りたたみ)   ┃
╰━━━━━━━━━━━━━━━━━━╯
N_FP_set.py
import time

DEFMAX = 999999

def main():
    # パラメータ入力
    print("How many toilets are there?")
    N = int(input())
    print("What number do you use?")
    firstUser = int(input())

    if (firstUser > N) or (firstUser < 1):
        print("can not choise")
        return

    print("-----------------------------")

    startTime = time.time()

    # N個の便器を生成
    toilets = [0] * N
    print_now(toilets)
    # 最初のトイレを使用中にする
    toilets[firstUser-1] = 1
    print_now(toilets)

    while True:
        # 現在何人が使用しているか
        nowPerson = max(toilets)
        leftbox = [0] * N
        leftCount = DEFMAX
        rightbox = [0] * N
        rightCount = DEFMAX
        for i in range(N):
            #左側から探索
            if toilets[i] == 0:
                leftCount += 1
            else:
                leftCount = 0
            leftbox[i]=leftCount
            #右側から探索
            if toilets[-1-i] == 0:
                rightCount += 1
            else:
                rightCount = 0
            rightbox[-1-i]=rightCount

        #今回使う便器を選択
        priorotyBox = [0]*N
        for i in range(N):
            priorotyBox[i] = min(leftbox[i], rightbox[i])
        if max(priorotyBox) < 2:
            break
        nowPerson += 1
        # 便器の情報を更新
        toilets[priorotyBox.index(max(priorotyBox))] = nowPerson
        print_now(toilets)

    endTime = time.time()
    timeDiff = endTime-startTime

    print("-----------------------------")
    print(f"use person : {max(toilets)}")
    print(f"utilization rate : {round(max(toilets)/N, 5)}")
    print(f"time:{round((timeDiff), 4)}[s]")


def print_now(toilets):
    now = max(toilets)
    print(f"{now:4}: ", end="")
    for i in range(len(toilets)):
        if (toilets[i] == now) and (now != 0):
            print("|x", end="")
        elif (toilets[i] != 0):
            print("|o", end="")
        else:
            print("| ", end="")
    print("|")


if __name__ == "__main__":
    main()

$7$個のトイレの$3$番目を利用した出力は以下のようになります。

How many toilets are there?
7
What number do you use?
3
-----------------------------
   0: | | | | | | | |
   1: | | |x| | | | |
   2: | | |o| | | |x|
   3: |x| |o| | | |o|
   4: |o| |o| |x| |o|
-----------------------------
use person : 4
utilization rate : 0.57143
time:0.0003[s]

出力例には0~4の行がありますが、これが上の例のStepを意味します。そのステップで埋まったトイレを"x"で、既に埋まっているトイレを"o"で示しています。縦に見ると、"x"で埋まったトイレは、その後も"o"となり利用し続けています。

この例では最初に3番目を利用し(FP)、次のStep2で右端のトイレが埋まります。そうして4Stepで計4人が利用できます。また利用率は$4/7=0.57143$となります。

しかしこの実装は計算量が$O(N^2)$になるため、あまり早くありません。例えば$N=1024$の場合には、FirstPosition一つ試行するのに私のPCでは$0.35$秒要しました。FirstPositionは$1$から$N/2$まで試したいので、$N$が大きい場合には、計算コストが大きいです。

傾向の確認

それでもこのコードを拡張し、$1$から$N$までのトイレの数の各場合の、最適なFirstPositionを選んだ場合の最大利用可能者数を計算するコードを総当りで作成しました。なお、このコードは、最低な選び方をした場合の最小利用可能者数も併せて出力します。

━━━━━━━━━━━━━━━━━━╮
┃    ソースコードを表示(折りたたみ)   ┃
╰━━━━━━━━━━━━━━━━━━╯
N_range_set.py
import time

DEFMAX = 999999

def main():
    # パラメータ入力
    print("Chceck range?")
    N = int(input())

    startTime = time.time()
    for i in range(1,N+1):
        checkN(i)
    endTime = time.time()
    timeDiff = endTime-startTime

    print("-----------------------------")
    print(f"time:{round((timeDiff), 4)}[s]")

def checkN(N):
    highRate = 0.0
    highUser = 0
    highFirst = 0
    lowRate = 1.0
    lowUser = 0
    lowFirst = 0
    for i in range(1,(N//2)+2):
        currentUser,currentRate = calcUtilizationRate(N, i)
        currentRate = round(currentRate, 8)
        if highRate < currentRate:
            highRate = currentRate
            highUser = currentUser
            highFirst = i
        if lowRate > currentRate:
            lowRate = currentRate
            lowUser = currentUser
            lowFirst = i
    print("-------------------------------------")
    print(f"{N} MAX SCORE firstPosition:{highFirst} user:{highUser}, Rate:{highRate}")
    print(f"{N} MIN SCORE firstPosition:{lowFirst} user:{lowUser}, Rate:{lowRate}")


def calcUtilizationRate(N,X):
    # N個の便器を生成
    toilets = [0] * N
    # 最初のトイレを使用中にする
    toilets[X-1] = 1

    while True:
        # 現在何人が使用しているか
        nowPerson = max(toilets)
        leftbox = [0] * N
        leftCount = DEFMAX
        rightbox = [0] * N
        rightCount = DEFMAX
        for i in range(N):
            #左側から探索
            if toilets[i] == 0:
                leftCount += 1
            else:
                leftCount = 0
            leftbox[i]=leftCount
            #右側から探索
            if toilets[-1-i] == 0:
                rightCount += 1
            else:
                rightCount = 0
            rightbox[-1-i]=rightCount

        #今回使う便器を選択
        priorotyBox = [0]*N
        for i in range(N):
            priorotyBox[i] = min(leftbox[i], rightbox[i])
        if max(priorotyBox) < 2:
            break
        nowPerson += 1
        # 便器の情報を更新
        toilets[priorotyBox.index(max(priorotyBox))] = nowPerson
    
    return max(toilets), max(toilets)/N #usePerson, utilizationRate



if __name__ == "__main__":
    main()

$N=100$を指定した場合の出力例の抜粋です。

Chceck range?
100
-------------------------------------
1 MAX SCORE firstPosition:1 user:1, Rate:1.0
1 MIN SCORE firstPosition:0 user:0, Rate:1.0
-------------------------------------
2 MAX SCORE firstPosition:1 user:1, Rate:0.5
2 MIN SCORE firstPosition:1 user:1, Rate:0.5
-------------------------------------
3 MAX SCORE firstPosition:1 user:2, Rate:0.66666667
3 MIN SCORE firstPosition:2 user:1, Rate:0.33333333
-------------------------------------
:
(略)
:
-------------------------------------
98 MAX SCORE firstPosition:33 user:49, Rate:0.5
98 MIN SCORE firstPosition:2 user:33, Rate:0.33673469
-------------------------------------
99 MAX SCORE firstPosition:33 user:49, Rate:0.49494949
99 MIN SCORE firstPosition:2 user:34, Rate:0.34343434
-------------------------------------
100 MAX SCORE firstPosition:33 user:49, Rate:0.49
100 MIN SCORE firstPosition:4 user:34, Rate:0.34
-----------------------------
time:1.4839[s]

$N=1024$を指定した場合に、$1$から$N$個までのトイレ数の最大利用可能者数の計算には、私のPCで17,000秒ほど要しました。

全部示すのは大変なので、そのうち$N=128$までの、最適なFirstPositionを選んだ場合の同時利用可能数と利用率を、表に示します。

N FirstPosition 同時利用可能 利用率
1 1 1 1
2 1 1 0.5
3 1 2 0.66666667
4 1 2 0.5
5 1 3 0.6
6 1 3 0.5
7 3 4 0.57142857
8 1 4 0.5
9 1 5 0.55555556
10 1 5 0.5
11 3 6 0.54545455
12 3 6 0.5
13 5 7 0.53846154
14 5 7 0.5
15 1 7 0.46666667
16 1 8 0.5
17 1 9 0.52941176
18 1 9 0.5
19 3 10 0.52631579
20 3 10 0.5
21 5 11 0.52380952
22 5 11 0.5
23 5 11 0.47826087
24 8 12 0.5
25 9 13 0.52
26 9 13 0.5
27 9 13 0.48148148
28 9 13 0.46428571
29 1 13 0.44827586
30 1 14 0.46666667
31 1 15 0.48387097
32 1 16 0.5
33 1 17 0.51515152
34 1 17 0.5
35 3 18 0.51428571
36 3 18 0.5
37 5 19 0.51351351
38 5 19 0.5
39 5 19 0.48717949
40 8 20 0.5
41 9 21 0.51219512
42 9 21 0.5
43 9 21 0.48837209
44 9 21 0.47727273
45 9 21 0.46666667
46 14 22 0.47826087
47 15 23 0.4893617
48 16 24 0.5
49 17 25 0.51020408
50 17 25 0.5
51 17 25 0.49019608
52 17 25 0.48076923
53 17 25 0.47169811
54 17 25 0.46296296
55 17 25 0.45454545
56 17 25 0.44642857
57 1 25 0.43859649
58 1 26 0.44827586
59 1 27 0.45762712
60 1 28 0.46666667
61 1 29 0.47540984
62 1 30 0.48387097
63 1 31 0.49206349
64 1 32 0.5
65 1 33 0.50769231
66 1 33 0.5
67 3 34 0.50746269
68 3 34 0.5
69 5 35 0.50724638
70 5 35 0.5
71 5 35 0.49295775
72 8 36 0.5
73 9 37 0.50684932
74 9 37 0.5
75 9 37 0.49333333
76 9 37 0.48684211
77 9 37 0.48051948
78 14 38 0.48717949
79 15 39 0.49367089
80 16 40 0.5
81 17 41 0.50617284
82 17 41 0.5
83 17 41 0.4939759
84 17 41 0.48809524
85 17 41 0.48235294
86 17 41 0.47674419
87 17 41 0.47126437
88 17 41 0.46590909
89 17 41 0.46067416
90 26 42 0.46666667
91 27 43 0.47252747
92 28 44 0.47826087
93 29 45 0.48387097
94 30 46 0.4893617
95 31 47 0.49473684
96 32 48 0.5
97 33 49 0.50515464
98 33 49 0.5
99 33 49 0.49494949
100 33 49 0.49
101 33 49 0.48514851
102 33 49 0.48039216
103 33 49 0.47572816
104 33 49 0.47115385
105 33 49 0.46666667
106 33 49 0.46226415
107 33 49 0.45794393
108 33 49 0.4537037
109 33 49 0.44954128
110 33 49 0.44545455
111 33 49 0.44144144
112 33 49 0.4375
113 1 49 0.43362832
114 1 50 0.43859649
115 1 51 0.44347826
116 1 52 0.44827586
117 1 53 0.45299145
118 1 54 0.45762712
119 1 55 0.46218487
120 1 56 0.46666667
121 1 57 0.47107438
122 1 58 0.47540984
123 1 59 0.4796748
124 1 60 0.48387097
125 1 61 0.488
126 1 62 0.49206349
127 1 63 0.49606299
128 1 64 0.5

利用率が0.5を超える場合

最適なFirstPositionを選んだ場合の、利用率が$0.5$より大きい数値を太字で示しました。$0.5$より高いスコアを抜粋すると下記になります。

$1,2,3,5,7,9,11,13,17,19,21,25,33,35,37,41,49,65,67,69,73,81,97$

このスコアは次の2パターンに分けることができます。

パターン① $N=2^X+1$である
例:
$2=2^0+1$
$5=2^2+1$
$17=2^4+1$ など
いずれもFirstPositionが1(端)になる。

パターン② パターン①$2個-1$の形式である
例:
$7=3+5-1 = (2^1+1)+(2^2+1)-1$
$13=5+9-1 = (2^2+1)+(2^3+1)-1$
$19=3+17-1 = (2^1+1)+(2^4+1)-1$
FirstPositionの位置で分割すると、その左右がパターン①となる。

このことから利用率が$0.5$を超えるような(奇数個のトイレで奇数番目がすべて埋まる)場合は、奇数の中でも$2^X+1$かその組み合わせで表せるトイレの場合に、適切なFirstPositionのトイレが存在します。

利用率が0.5に等しい

利用率が0$.5$となるのは偶数個のトイレで、半分のトイレがすべて埋まっている場合です。これは上の$128$までの表を確認すると、上述のパターン①もしくはパターン②の$\pm1$の個数のトイレの場合に、$0.5$になることが分かります。

例:
$16 = 17-1$  ※$17$はパターン①
$22 = 21+1$  ※$21$はパターン②

利用率が0.5未満

このパターンには、トイレの数を$N$個とした場合に$N$の前後での利用率が$(>0.5)$のトイレの数から探索できます。

例えば$N=29$を考えてみましょう。その前後を抜粋しました。

N FirstPosition 同時利用可能 利用率
25 9 13 0.52
26 9 13 0.5
27 9 13 0.48148148
28 9 13 0.46428571
29 1 13 0.44827586
30 1 14 0.46666667
31 1 15 0.48387097
32 1 16 0.5
33 1 17 0.51515152

$N=29$の前後で利用率が$0.5$より大きくなるのは、$N=25$および$N=33$です。

まずは$29$より低い$N=25$から近づくアプローチを確認します。$N=26,27,28$はFirstPositionの位置は9番目、同時利用可能数も13で$N=25$と変わりません。これはFirstPositionの$9$を含む左側は$5$人が利用できることは変わらず、FirstPositionの右側ではいずれの$N$でも$8$人が利用できます。しかし、使えないデッドスペースが$1$個ずつ増えていきます。

以下に、$25$から$29$までのトイレの個数の場合に、FirstPositionを$9$と固定し、最終的に埋まったトイレの配置を示します。なお最初のユーザを"fp"、埋まったトイレを"o"で示しています。

N=25:|o| |o| |o| |o| |fp| |o| |o| |o| |o| |o| |o| |o| |o|

N=26:|o| |o| |o| |o| |fp| |o| |o| |o| |o| |o| |o| |o| | |o|

N=27:|o| |o| |o| |o| |fp| |o| |o| |o| | |o| |o| |o| |o| | |o|

N=28:|o| |o| |o| |o| |fp| |o| |o| |o| | |o| |o| | |o| |o| | |o|

N=29:|o| |o| |o| |o| |fp| |o| | |o| |o| | |o| |o| | |o| |o| | |o|

トイレが$25$個の場合には、奇数番目が全て埋まっていますが、$26$から一つずつ増加するに連れて、使えないトイレが増えていくのが分かります。

逆に$29$より大きい$N=33$から近づくアプローチを考えます。この範囲ではFirstPositionは$1$固定です。これもまず図示してみます。

N=29:|fp| | |o| |o| |o| | |o| |o| |o| | |o| |o| |o| | |o| |o| |o|

N=30:|fp| | |o| |o| |o| | |o| |o| |o| | |o| |o| |o| |o| |o| |o| |o|

N=31:|fp| | |o| |o| |o| |o| |o| |o| |o| | |o| |o| |o| |o| |o| |o| |o|

N=32:|fp| | |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o|

N=33:|fp| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o| |o|

この範囲では、$N=33$から見て、トイレの数が一つずつ小さくなるに連れて、2個ならんだ空きトイレが増えていくことが分かります。なお、着目した$N=29$は、$N=25$の下からのアプローチと$N=33$の上からのアプローチの結果の利用できるユーザが$13$人でどちらでも同じ結果になります。PythonスクリプトではFirstPositionが若い方を優先したため、表ではFirstPositionが$1$の結果を表に記載しました。

なお、参考までに「FirstPositionが$1$の$N=28$」と「FirstPositionが$9$の$N=30$」出力してみます。

「FirstPositionが$1$の$N=28$」の場合

N=28:|fp| | |o| | |o| | |o| |o| |o| | |o| |o| |o| | |o| |o| |o|

結果:$12$人利用可能(利用率:0.42857)
よって、FirstPositonが$9$の場合には13人が利用可能なので、こちらのほうが低い。


「FirstPositionが$9$の$N=30$」の場合
N=30:|o| |o| |o| |o| |fp| |o| | |o| |o| | |o| |o| | |o| | |o| | |o|

結果:$13$人が利用できる。(利用率:0.43333)
よって、FirstPositonが$1$の場合には14人が利用可能なので、こちらのほうが低い。

このように、結果の$N=29$の前後での、FirstPosition選択の妥当性を確認できました。

最大の利用率の計算アルゴリズムの見直し

ここまでの分析で、トイレの数$N$に対して、最大の利用率と適切なFirstPositionの探索方法がわかりました。そこでループ探索ではなく、次のようなコードを実装しました。

  • 入力$N$に対し、$N=2^X + 1$であれば、$\frac{N}{2} + 1$人が利用できる(パターン①)
     この時FirstPositionは1になる

  • 入力$N$に対し、パターン①2つの重ね合わせである
      $N=(2^X + 1) + (2^Y+1)-1$の場合にも、$\frac{N}{2} + 1$人が利用できる(パターン②)
     このときFirstPositionはパターン①の重なる部分になる。

  • パターン①およびパターン②の前後のトイレ数の場合には、$\frac{N}{2}$人が利用できる(パターン③)
     このときFirstPositionは、前後のパターン①or②の位置と同じ

  • それ以外の$N$の場合は、近くのパターン①または②の位置から、計算する

この方針で実装したコードが下記です。

━━━━━━━━━━━━━━━━━━╮
┃    ソースコードを表示(折りたたみ)   ┃
╰━━━━━━━━━━━━━━━━━━╯
Range_MAX_Calc.py
import itertools
import time

NMAX = 99999

def main():
    # パラメータ入力
    print("How many toilets are there?")
    N = int(input())

    start_time = time.time()
    for i in range(1, N+1):
        calc(i)
    end_time = time.time()
    timeDiff = end_time - start_time
    print("------------------------------------------")
    print(f"time:{round((timeDiff), 4)}[s]")


def calc(N):
    list2n_plus1 = []

    count = 1
    while True:
        list2n_plus1.append(2**count + 1)

        if list2n_plus1[-1] > NMAX:
            break
        count+=1

    ic = itertools.combinations(list2n_plus1, 2)
    list_make2_2n = []
    for key in ic:
        now2_2n = key[0]+key[1]-1
        if now2_2n <= NMAX:
            list_make2_2n.append([now2_2n, key[0]])
    list_make2_2n.sort()

    if N <= 2: # N=2以下
        user = 1
        fp = 1
    elif N in list2n_plus1: # Nが2のべき乗+1 ptn1
        user = N//2+1
        fp = 1
    elif N in [x[0] for x in list_make2_2n]: #[Nが2のべき乗+1]の和 ptn2
        user = N//2+1
        indexes = [i for i, x in enumerate(list_make2_2n) if x[0] == N]
        fp = list_make2_2n[indexes[0]][1]
    elif (N in [x+1 for x in list2n_plus1]): #ptn1の+1
        user = N//2
        fp = 1
    elif (N in [x-1 for x in list2n_plus1]): #ptn1の-1
        user = N//2
        fp = 1
    elif (N in [x[0]+1 for x in list_make2_2n]):#ptn2の+1
        user = N//2
        indexes = [i for i, x in enumerate(list_make2_2n) if x[0] == N-1]
        fp = list_make2_2n[indexes[0]][1]
    elif (N in [x[0]-1 for x in list_make2_2n]): #ptn2の-1
        user = N//2
        indexes = [i for i, x in enumerate(list_make2_2n) if x[0] == N+1]
        fp = list_make2_2n[indexes[0]][1]-1
    else:#それ以外
        for i in range(len(list2n_plus1)):
            if list2n_plus1[i] >= N:
                ptn1_high = i
                ptn1_low = i-1
                break
                
        for i in range(len(list_make2_2n)):
            if list_make2_2n[i][0] >= N:
                ptn2_high = i
                ptn2_low = i-1
                break
        
        #ptn1とptn2で近いスコアを探す
        #低い方からの探索は必ずptn2
        if list_make2_2n[ptn2_low][0] <= list2n_plus1[ptn1_low]:
            lowuser = list2n_plus1[ptn1_high]//2+1
            lowfp = 1
            if(N==15):
                print("x")
        else:
            lowuser = list_make2_2n[ptn2_low][0]//2+1
            lowindexes = [i for i, x in enumerate(list_make2_2n) if x[0] == list_make2_2n[ptn2_low][0]]
            lowfp = list_make2_2n[lowindexes[0]][1]

        #高い方からの探索
        if list2n_plus1[ptn1_high] < list_make2_2n[ptn2_high][0]:
            diff = list2n_plus1[ptn1_high] - N
            highuser = list2n_plus1[ptn1_high]//2+1-diff
            highfp = 1
        else:
            diff = list_make2_2n[ptn2_high][0] - N
            highuser = list_make2_2n[ptn2_high][0]//2+1 - diff
            highindexes = [i for i, x in enumerate(list_make2_2n) if x[0] == list_make2_2n[ptn2_high][0]]
            highfp = list_make2_2n[highindexes[0]][1] - diff
            if(N==15):
                print("z")
        
        if (highuser==lowuser) and (highfp==1):
            user = highuser
            fp = highfp
        elif highuser<=lowuser:
            user = lowuser
            fp = lowfp
        else:
            user = highuser
            fp = highfp

    rate = round(user/N,8)
    print(f"{N},{fp},{user},{rate}")   

if __name__ == "__main__":
    main()

このコードで$N=1$から$1024$をまとめて計算したところ 0.1258 秒で計算することができました。元の実装だと17,000秒ほど要していたので、かなり改善しました。また計算結果のFirstPositionおよび利用者数数、利用率も、一致することを確認しています。

$N=10000$でも 1.2978秒で計算できています。なお、より大きい値を計算したい場合には、最大値と仮置きした定数NMAXをもっと大きい値に変更してください。

おまけ

ここまでの検討では多くの人が利用できるパターンについて分析しました。
反対に少しの人だけが利用できる(=反対に多くの人が利用できなくなる)FirstPositionを選んだ場合、どうなるのかを考えてみます。計算結果図示します。

min_plot.png

横軸がトイレの数$N$です。左目盛りの縦軸(青)が最小を選ぶ場合のFirstPositionで、赤線の右目盛りが同時利用可能な(最小)利用者数です。

構造に着目すると青線のFirstPositionはフラクタルな周期構造が表れていることがわかります。青が1に戻る$N$は以下のようになります。
$N=2,4,6,11,21,41,81,161,321,641$
それぞれ-1すると、
$1,3,5,10,20,40,80,160,320,640$
となり、$5$以降は、倍々で増えていることがわかります。各周期で現れる山は回を追うごとに高くなっており、各山の右上の点は同じ縮尺にした最小利用者数の赤線と重なることがわかります。

山の頂点部分などFirstPositionが変わらない(横線の)部分では赤線の利用者数が増え、FirstPositionが増える(傾きが急な)領域では、赤線の利用者が増えずに平らである関係があることがわかります。

少ない利用者しか利用できないようなFPの選び方は、一般化まではできていないですが、このような傾向があるごとがわかりました。

最後に

こんな記事ですが、最後まで読んでいただき、ありがとうございます。
何かお気づきの点がありましたら、コメントなど大歓迎です。

トイレを選ぶときには、なるべく多くの人が利用できるような場所を、積極的に選んで生きていきます。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?