カロリーから考える丸亀2000円チャレンジ

この記事の主張:丸亀2000円チャレンジにおいて、2021/10/11時点でのレギュ+店舗限メニュー禁止の条件下では、「おろし醤油うどん並」と「いか天」「いなり」「梅おにぎり」「明太子おにぎり」「鮭おにぎり」「こんぶおにぎり」をそれぞれ2つずつ注文するのが、一番カロリーが低い組み合わせである。

ねりうめです。何故か最近身内で「丸亀2000円チャレンジ」が流行っています。丸亀製麺で2000円分食べるチャレンジです。自分もやりました。

様々な攻略法が考案されていますが、カロリーの観点から考察されたものが見当たらなかったので、自分の勉強も兼ねて調べてみました。

画像1

各メニューのカロリーは、ここここを参照しました。計算に使用したメニューは、公式HPに掲載されているもののうち、「店舗限定」が書かれていないもののみです。

結論は冒頭に示したとおりです。ぜひお試しください。
以下に、導出過程を示します。


①考えるうどんの絞り込み
本レギュレーションは使用可能うどんが1杯のみなので、まず、どのうどんを食べるか決めることにした。ただし、すべてのうどんを考慮する必要はない。今回はカロリーを最小化するのが目的であるので、「同価格のうち、最もカロリーが低いうどん」についてのみ考慮すればよい。また、「他のうどんより価格が安く、カロリーが高い」うどんについても省いて良い。以下の表が、この条件に当てはまるうどんである。

画像2

次に、うどん以外のメニューについて調査した。野菜かき揚げバグってるでしょ。

画像3

では、これらをどのように組み合わせれば、最もカロリーの低い組み合わせを見つけられるだろうか。とりあえず、総当りで試してみることにした。

(ここから先はプログラミングの話)

udon = [
       [390, 297, "おろし醤油うどん並"],
       [460, 400, "とろ玉うどん温並"],
       [470, 434, "きつねうどん並"],
       [500,	438,	"おろし醤油うどん大"],
       [570,	544,	"とろ玉うどん温大"],
       [580,	579,	"きつねうどん大"],
       [610,	604,	"おろし醤油うどん得"],
       [680,	786,	"とろ玉うどん得"],
       [690,	855, "きつねうどん得"],
       [730,	1068,	"カレーうどん得"]
       
]
sidemenu = [
         [150,	182,	"かしわ天"],
         [130,	106,	"いか天"],
         [120,	159,	"さつまいも天"],
         [110,	151,	"かぼちゃ天"],
         [120,	156,	"ちくわ天"],
         [420,	460,	"親子丼"],
         [120,	121,	"いなり"],
         [140,	144,	"梅"],
         [140,	147,	"鮭"],
         [140,	147,	"明太子"],
         [140,	153,	"こんぶ"],

]

price_goal = 2000

# ①総当りで解いてみる
def rec(price_now, cal_now, buy_list):
   if (price_now < 2000):
     temp_answer = [0,1e+10,""]
     for i in range(len(sidemenu)):
       if (buy_list[i] < 2):
         new_buy_list = list(buy_list)
         new_buy_list[i] += 1
         t = rec(price_now + sidemenu[i][0], cal_now + sidemenu[i][1], new_buy_list)
         if (t[1] < temp_answer[1]):
           temp_answer = list(t)
     return temp_answer
   else:
     ans_list = [price_now, cal_now, buy_list]
     return ans_list

def brute_force(id):
 price_sum = udon[id][0]
 cal_sum = udon[id][1]
 answer = rec(price_sum, cal_sum, [0] * len(sidemenu))
 return answer

print(brute_force(9))

このプログラムでは、各トッピングに対し、(0,1,2)個購入するパターンを全て試し、その中で一番カロリーが少なく済むものを出力している。

とりあえず全てのパターンについて調べるので、とても長い時間がかかってしまう。一番実行時間が短く済む「カレーうどん得」についてでも、約2時間ほどかかってしまった。

流石にやってられないので、別の手法を試した。

# ②貪欲的に解く
sidemenu_cp = sorted([[x[1]/x[0], x[0], x[1], x[2]] for x in sidemenu])


def greedy(udon_id):
   price_now = udon[udon_id][0]
   cal_now = udon[udon_id][1]
   ans = []
   p = 0
   count = 0
   while(price_now < 2000):
       price_now += sidemenu_cp[p][1]
       cal_now += sidemenu_cp[p][2]  # カロリー
       ans.append(sidemenu_cp[p][3])
       count += 1
       if(count == 2):
           p += 1
           count = 0
   return [udon[udon_id][2], price_now, cal_now, ans]


answer = ["", 0, 1e+10, []]

for u in range(len(udon)):
   a = greedy(u)
   print(a)
   if(a[2] < answer[2]):
       answer = a.copy()
print(answer)

あらかじめサイドメニューのコスパを計算しておき、コスパのよいものから順にぶちこむことにした。実行結果は以下の通りである。

画像4

うどん名, 値段, カロリー, メニューの順に表示されている。
しかし、これは正しい解ではない。
例えば、「おろし醤油うどん大」については、おにぎり3つをかしわ天2つに替えたほうがカロリーは低くなる。

そこで、厳密な解を求めるために、動的計画法を用いる。詳細は省くけど、こういうとき強いアルゴリズムである。

# ③動的計画法を用いる
udon = [
   [390,   297,    "おろし醤油うどん並"],
   [460,   400,    "とろ玉うどん温並"],
   [470,   434,    "きつねうどん並"],
   [500,   438,    "おろし醤油うどん大"],
   [570,	544,    "とろ玉うどん温大"],
   [580,	579,    "きつねうどん大"],
   [610,	604,    "おろし醤油うどん得"],
   [680,	786,    "とろ玉うどん得"],
   [690,	855,    "きつねうどん得"],
   [730,	1068,   "カレーうどん得"]

]
sidemenu = [
   [150,	182,	"かしわ天"],
   [130,	106,	"いか天"],
   [120,	159,	"さつまいも天"],
   [110,	151,	"かぼちゃ天"],
   [120,	156,	"ちくわ天"],
   [420,	460,	"親子丼"],
   [120,	121,	"いなり"],
   [140,	144,	"梅"],
   [140,	147,	"鮭"],
   [140,	147,	"明太子"],
   [140,	153,	"こんぶ"],
   # 2つまでの制約をかけるのが面倒だからこっちを2倍する
   [150,	182,	"かしわ天"],
   [130,	106,	"いか天"],
   [120,	159,	"さつまいも天"],
   [110,	151,	"かぼちゃ天"],
   [120,	156,	"ちくわ天"],
   [420,	460,	"親子丼"],
   [120,	121,	"いなり"],
   [140,	144,	"梅"],
   [140,	147,	"鮭"],
   [140,	147,	"明太子"],
   [140,	153,	"こんぶ"]
]
price_goal = 2000
cal_limit = 3000
dp = [[0] * (cal_limit + 1) for _ in range(len(sidemenu) + 1)]

for i in range(len(sidemenu)):
   for j in range(3000):
       if(j >= sidemenu[i][1]):
           dp[i+1][j] = max(dp[i][j], dp[i]
                            [j-sidemenu[i][1]] + sidemenu[i][0])
       else:
           dp[i+1][j] = dp[i][j]

for u in udon:
   for i in range(3000):
       if (u[0] + dp[len(sidemenu)][i] >= 2000):
           print(u[2] + ": "+str(u[1] + i))
           break

実行結果は以下の通り。

画像5

使用うどんとカロリーが表示されている。単純な貪欲と比べると、差があるものがある。

結局さっきの貪欲で解いたパターンが最強だったらしい。たまたまだけど。
というわけでみなさんも丸亀2000円チャレンジしよう!自分もやったので!

colabでのコード置いときます

この記事が気に入ったらサポートをしてみませんか?