2014年12月27日土曜日

プロジェクトオイラー 問100

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問100「組み合わせの確率」
箱の中に15個の青い円盤と6個の赤い円盤からなる21個の色のついた円盤が入っていて, 無作為に2つ取り出すとき, 青い円盤2つを取り出す確率P(BB)は
P(BB) = (15/21) × (14/20) = 1/2
であることがわかる.

無作為に2つ取り出すとき, 青い円盤2つを取り出す確率がちょうど1/2となるような次の組み合わせは, 箱が85個の青い円盤と35個の赤い円盤からなるときである.

箱の中の円盤の合計が1012 = 1,000,000,000,000を超えるような最初の組み合わせを考える. 
箱の中の青い円盤の数を求めよ.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。
def f(n):
	x, y, i = 1, 1, 0
	while i<=n:
		x, y = x+y*2, x+y
		if x*y%2: i, j = (x+1)/2, (y+1)/2
	return i,j

n = 10**12
s, b = f(n)
print "blue : ", b
print "red  : ", s-b
print "sum  :", s


問題のまま実装するととても1分では処理が終わらず、プログラミングの前に数学的に解法を整理しておかないと解けないという、プロジェクトオイラーらしい問題です。

円盤総数をi個、青円盤数をj個とすると、定義から以下が成り立ちます。
j/i * (j-1)/(i-1) = 1/2
2j(j-1) = i(i-1)
i2-i - 2(j2-j) = 0
(i - 1/2)2-(1/4) - 2(j - 1/2)2 + (1/2) = 0
(i - 1/2)2 - 2(j - 1/2)2 + 1/4 = 0
4(i- 1/2)2 - 8(j - 1/2)2 + 1 = 0
(2i-1)2 - 2(2j-1)2 = -1  ...(a)
ここで、媒介変数xとyを
x = 2i-1, y = 2j-1 ...(b)
とおくと、
x2 -  2y2 = -1 ...(c)
これは、プロジェクトオイラーではおなじみのペル方程式で、
円盤総数i=(x+1)/2が、nを超えた最初の解のときの青円盤数j=(y+1)/2が求める値です。

問94 より、ペル方程式の公式は以下の通り。
x2 - Dy2 = ±1 (Dは平方数以外。平方数ならば整数解が存在しない)
の解は、最小整数解を(x0, y0)、それ以降の解を順に、(x1, y1), (x2, y2)、...として、以下の連立方程式が成り立つ。
┌xn+1 = x0*xn + D*y0*yn
└yn+1 = y0*xn +   x0*yn ...(d)

最小整数解(x0, y0)は、式(c)から明らかで、x0=1, y0=1。また式(c)からD=2。
これらから連立方程式(d)は以下のとおり。
┌xn+1 = xn + 2yn
└yn+1 = xn + yn ...(e)
なお、式(a)より、xnとynは両方とも奇数。
これを実装します。

1.関数f(n)
・x, y, i = 1, 1, 0
 媒介変数xとyの初期値はともに1、総円盤数iの初期値は0としておきます。

・while i<=n:
 総円盤数iが指定値nを超えるまで処理を行います。

・x, y = x+y*2, x+y
 連立方程式(e)を実装します。

・if x*y%2: i, j = (x+1)/2, (y+1)/2
 媒介変数xとyの両方が奇数という判定は以下のとおりです。
 xとyの両方が奇数のときだけ、その積が奇数になるので、
 xとyの積を2で割った余りが1(論理判定ではTrue)になるかで判定します。
 そして、xとyが両方とも奇数のときに、式(b)から割り戻した値を
 円盤総数iと青円盤数jに退避しておきます。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
blue :  756872327473
red  :  313506783024
sum  : 1070379110497

2014年12月20日土曜日

プロジェクトオイラー 問99

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問99「最大のべき乗」
指数の形で表される2つの数, 例えば 211 と 37, の大小を調べることは難しくはない. 電卓を使えば, 211 = 2048 < 37 = 2187 であることが確かめられる.

しかし, 632382518061 > 519432525806 を確認することは非常に難しい (両者ともに300万桁以上になる).

各行に1組が書かれている1000個の組を含んだ22Kのテキストファイル base_exp.txt から, 最大の数が書かれている行の番号を求めよ.

注: ファイル中の最初の二行は上の例である.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。

# -*- coding:sjis -*- def f(sIn): import math pIn = open(sIn) c = 0 for i, r in enumerate(pIn.readlines()): a, b = r[:-1].split(",") v = int(b)*math.log10(int(a)) if c<v: c, line, rec, digit = v, i+1, r[:-1], int(v)+1 pIn.close() pIn = None return line, rec, digit sIn = os.path.join(os.path.dirname(__file__),"p099_base_exp.txt") line, rec, digit = f(sIn) print "line :", line print "rec  :", rec print "digit:", digit


1.関数f(sIn)
・aのb乗をpとすると、
 p = ab
 両辺の10を底とする対数をとって、
 log(p) = b x log(a)
 なお、上記式の両辺の対数の底を10にすれば、
 この両辺の値を切り捨て+1した値が桁数になります。
 この式を実装して最大値を判定します。
 
・for i, r in enumerate(pIn.readlines()):
 readline関数で1行ずつ読み込みます。

・a, b = r[:-1].split(",")
 readline関数で末尾の改行コードは取り除かないので、末尾の1バイトを削除し、半角カンマで基数と指数に分けます。

・v = int(b)*math.log10(int(a))
 vは、aのb乗の値の、10を底とする対数値です。

・if c<v: c, line, rec, digit = v, i+1, r[:-1], int(v)+1
 対数値vの最大値をcにためます。
 iは0から始まる行カウンタなので、行番号はi+1です。
 readline関数で末尾の改行コードは取り除かないので、行内容recは末尾の1バイトを削除します。
 digitは桁数です。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
line : 709
rec  : 895447,504922
digit: 3005316

2014年12月10日水曜日

プロジェクトオイラー 問98

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問98「アナグラム平方数」
CARE という単語の各文字をそれぞれ 1, 2, 9, 6 に置き換えることによって, 
平方数 1296 = 362 ができる. 注目すべきことに, 同じ数字の置換をつかうことにより, アナグラムの RACE も平方数 9216 = 962 をつくる. 
CARE (と RACE) を平方アナグラム単語対と呼ぼう. 先頭のゼロは許されず, 
異なる文字が同じ数字をもつこともないとする.

約 2,000 個の一般的な英単語を含む 16K のテキストファイルwords.txt 

(右クリックして "名前をつけてリンク先を保存") を用いて, 
平方アナグラム単語対をすべて求めよ (回文となる単語はそれ自身のアナグラムとはみなさない).

そのような対のメンバーから作られる最大の平方数は何か?

注: 作られるアナグラムは, すべて与えられたテキストファイルに含まれている.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。
def f(sIn):
	import itertools
	W = {}
	pIn = open(sIn)
	for s in pIn.read().replace('"', '').split(","):
		t = "".join(sorted(s))
		W[t] =W.get(t, [])+[s]
	pIn.close()
	pIn = None
	
	for k,v in W.items():
		if len(v)<2: W.pop(k)
		
	N, m = {}, max([len(s) for s in W])
	for i in xrange(int((10**m)**.5)+1):
		s = str(i*i)
		t = "".join(sorted(s))
		N[t] = N.get(t, [])+[s]
	
	for k,v in N.items():
		if len(v)<2: N.pop(k)
		
	nm = 0
	for s in W:
		for wp in itertools.permutations(W[s], 2):
			
			for t in N:
				if len(s)!=len(t): continue
				if len(set(s))!=len(set(t)): continue
				
				for np in itertools.permutations(N[t], 2):
					p = dict([(u, v) for (u, v) in zip(wp[0], np[0])])
					if "".join([p[u] for u in wp[1]])!=np[1]: continue
					i = int(max(np))
					if nm<i: nm, wpm, npm = i, wp, np
					
	return nm,wpm,npm,int(nm**.5)
	
sIn = os.path.join(os.path.dirname(__file__), "p098_words.txt")
s = f(sIn)
print s


1.関数f(sIn)
・単語ファイルsInを読み込んで、問題に合う最大平方数nm、その場合の単語ペアwpmと二乗値ペアnpm、最大平方数の+の平方根を返します。

・単語辞書Wとして、キーにソート済み構成文字、値にそのアナグラム単語のリストを持つ辞書を作成します。
 アナグラムにならない単語、つまり単語辞書で値リストの要素数が2つ未満の場合は単語辞書からpop関数で削除します。
・同様に二乗値の方も、
 二乗値辞書Nとして、キーにソート済み構成数字、値にそのアナグラム二乗値のリストを持つ辞書を作成します。
 ただし、候補単語の最大文字列長mから最大桁数を計算し、二乗値辞書にはその桁数以下の値を対象にします。
 またアナグラムにならない二乗値、つまり二乗値辞書で値リストの要素数が2つ未満の場合は二乗値辞書からpop関数で削除します。

・単語辞書の値から、アナグラム単語ペアを順列(順番が変われば別扱いとした全組合せ)で作り出します。
 アナグラム単語が2つならば2P2=2通り、3つならば3P2=6通りのペアを作り出します。
・同様に二乗値の方も、アナグラム二乗値ペアを作り出します。
・単語と二乗値とで、桁数と構成文字種類数が同じ場合に比較していきます。
 桁数はlen関数で取得し、構成文字種類数はset関数で構成文字をユニークにしてからlen関数で個数を取得します。

 
・単語ペアwpと二乗値ペアnpのそれぞれ1つ目の値から、文字と数字の一字対応辞書pを作成します。
 この一字対応辞書で単語ペアの2つ目を翻訳して二乗値ペアの2つ目になるかをチェックします。
 チェックNGの場合は次の候補値へ進みます。
 単語ペアと辞書ペアを順列で作り出しているのは、一字対応辞書を作成元をペア1つ目に固定して、
 そのチェック対象をペア2つ目に固定しているためです。
 順番無関係の組合せcombinationではなく、順番が関係する順列permutationsを使用するのはこのためです。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
(18769, ('BOARD', 'BROAD'), ('17689', '18769'), 137)

2014年11月24日月曜日

プロジェクトオイラー 問97

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問97「大きな非メルセンヌ素数」
100万桁を超える初めての素数は1999年に発見された. これはメルセンヌ素数であり, 26972593-1 である. 実際, 2,098,960桁ある. 
それ以降も, より多くの桁になるメルセンヌ素数 (2p-1の形の数) が他にも発見されている.

しかし, 2004年に, 非常に大きな非メルセンヌ素数が発見された. 
これは2,357,207桁の数であり, 28433×27830457+1である.

この素数の末尾10桁を答えよ.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。
def f(a, n, p, b, m):
	k, t, c = n, 10**m, 1
	for s in str(p)[::-1]:
		c *= k**(int(s))%t
		k = k**10%t
	return (a*c+b)%t

def f2(a, p, b, m): return ((a<<p)+b)%(10**m)

#(a×(nのp乗)+b)の末尾m桁を返す
a, n, p ,b ,m =28433, 2, 7830457, 1,10
s = f(a, n, p ,b ,m)
print s

#(a×(2のp乗)+b)の末尾m桁を返す
a, p ,b ,m =28433, 7830457, 1,10
s = f2(a, p ,b ,m)
print s


1.関数f(a, n, p, b, m)
・a×np+b の末尾m桁を返します。

・for s in str(p)[::-1]:
 数値pをstr関数で文字型にして、文字のスライス機能の[開始:終了:刻み]での刻みを-1にすることで右から1文字ずつ、つまり1の位の数字から順に1つずつ取り出し、ループ変数sに設定します。

・c *= k**(int(s))%t
 k = k**10%t
 変数kは、ループ変数sの桁位置に相当するnの累乗値を設定します。
 初期値はnで、ループが回るにつれ、nの10回累乗、100回累乗、...と直前の累乗値を10回ずつ累乗していきます。
 変数cに、kのs乗をかけて、ためていきます。
 例えば、
 2の345乗ならば、(2の5乗) x ((2の10乗))の4乗) x ((2の100乗)の3乗)です。
 このときの「2の10乗」「2の100乗」が変数kで、各桁の数字が変数sです。

2.(別解)関数f2(a, p ,b ,m)
・a×2p+b の末尾m桁を返します。
 関数の中の累乗計算を、2の累乗に固定します。

・a<<p
 数値aをpビット左にシフトすることで、2進数でp桁繰り上がります。
 これは、数値aに対して2をp回かけることと同じです。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
8739992577

2014年11月4日火曜日

プロジェクトオイラー 問96

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問96「数独」
"数独"(日本語で数字を配置するという意味)とは人気があるパズルの名前である.
起源は不明だが, その評判はラテン語で"Squares"と呼ばれる同様な, 
そしてはるかに難しいパズルを考案したレオンハルト・オイラーの貢献によるものに違いない. 
しかしながら, "数独"パズルの目的はそれぞれの行, 列が3×3の枠を含む9×9の格子の空白(もしくは0)をそれぞれ1から9の数字で置き換えることである. 
下に, 一般的なパズルの開始状態とその解答の例がある.



うまく作られている"数独"パズルは, 選択肢を消去するために"仮定とテスト"方式を用いる必要があるかもしれないが, ただ一つの解を持ち, 論理によって解くことができる(これについては様々な意見がある).

探索の複雑さがパズルの難易度を決定する; 上に挙げた例は, 単純で直接的な推論によって解く事ができるため, 簡単であると考えられる.

6kバイトのテキストファイルsudoku.txt(右クリックで,"名前をつけてリンク先を保存") にはただ一つの解を持つ, 様々な難易度の50の"数独"パズルが含まれている(上の例題はこのファイルにおける最初のパズルである).

50すべてのパズルを解き, それぞれの解答の左上隅にある3桁の数の合計が求めよ; 
例えば483は上の解答例の左上隅の3桁の数である.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。







私の回答例は以下の通りです。
def d0(d):
	for x,y in Lxy():
		if not d[x,y]: d[x,y] = list(xrange(1,10))
	return d

def d1(d):
	for x,y in Lt(d):
		d[x, y] = sorted(set(d[x, y])-set(Ka(d,x,y)))
	return d

def d2(d):
	for x,y in Lt(d):
		for L in [Ltx(d,y), Lty(d,x), Ltm(d,x,y)]:
			M = [s for i,j in L for s in d[i,j]]
			N = [s for s in d[x,y] if M.count(s)==1]
			if N: d[x,y] = N
	return d

def d3(d):
	for n in xrange(1, 10):
		setn = set([n])
		for x,y in Lm3():
			N = [(i,j) for i,j in Ltm(d,x,y) if (n in d[i,j])]
			if len(N)!=2: continue
			if N[0][0]==N[1][0]:
				for i,j in Lty(d, N[0][0]):
					if (i,j) not in N: d[i,j] = sorted(set(d[i,j])-setn)
			if N[0][1]==N[1][1]:
				for i,j in Ltx(d, N[0][1]):
					if (i,j) not in N: d[i,j] = sorted(set(d[i,j])-setn)
	return d

def d4(d):
	for n in xrange(1, 10):
		setn = set([n])
		for s in xrange(9):
			L = [j for i,j in Lty(d,s) if (n in d[i,j])]
			if len(L)!=2: continue
			for i in sorted(set(xrange(9))-set([s])):
				N = [j for i,j in Lty(d,i) if (n in d[i,j])]
				if len(N)!=2: continue
				if sorted(L)!=sorted(N): continue
				for j in L:
					for k,j in Ltx(d,j):
						if k not in [s,i]: d[k,j] = sorted(set(d[k,j])-setn)
					
		for s in xrange(9):
			L = [i for i,j in Ltx(d,s) if (n in d[i,j])]
			if len(L)!=2: continue
			for j in sorted(set(xrange(9))-set([s])):
				N = [i for i,j in Ltx(d,j) if (n in d[i,j])]
				if len(N)!=2: continue
				if sorted(L)!=sorted(N): continue
				for i in L:
					for i,k in Lty(d,i):
						if k not in [s, j]: d[i,k] = sorted(set(d[i,k])-setn)
						
	return d

def d5(d):
	for x in xrange(9): d = f5(d, Lty(d, x), Vty(d, x))
	for y in xrange(9): d = f5(d, Ltx(d, y), Vtx(d, y))
	for x,y in Lm3(): d = f5(d, Ltm(d, x, y), Vtm(d, x, y))
	return d

def f5(d, Li, Lv):
	L = [s for s in Lv if len(s)==2]
	M = sorted(set([tuple(s) for s in L if Lv.count(s)==2]))
	M = [list(s) for s in M]
	for L in M:
		for n in L:
			setn = set([n])
			for i, j in Li:
				if d[i, j]!=L: d[i, j] = sorted(set(d[i, j])-setn)
	return d

def Lxy(): return [(x, y) for x in xrange(9) for y in xrange(9)]
def Lm(x, y): return [(x//3*3+i, y//3*3+j) for j in xrange(3) for i in xrange(3)]
def Lm3(): return [(x, y) for x in xrange(0,9,3) for y in xrange(0,9,3)]

def Lt(d): return [(x, y) for x,y in Lxy() if isinstance(d[x,y], list)]
def Ltx(d,y): return [(x, y) for x in xrange(9) if isinstance(d[x,y], list)]
def Lty(d,x): return [(x, y) for y in xrange(9) if isinstance(d[x,y], list)]
def Ltm(d,x,y): return [(x, y) for x, y in Lm(x,y) if isinstance(d[x,y], list)]

def Vx(d, y): return [d[x, y] for x in xrange(9)]
def Vy(d, x): return [d[x, y] for y in xrange(9)]
def Vm(d, x, y): return [d[x, y] for x,y in Lm(x, y)]
def Va(d, x, y): return Vx(d, y)+Vy(d, x)+Vm(d, x, y)

def Vtx(d, y): return [s for s in Vx(d, y) if isinstance(s, list)]
def Vty(d, x): return [s for s in Vy(d, x) if isinstance(s, list)]
def Vtm(d, x, y): return [s for s in Vm(d, x, y) if isinstance(s, list)]

def Kx(d, y): return [s for s in Vx(d, y) if isinstance(s, int)]
def Ky(d, x): return [s for s in Vy(d, x) if isinstance(s, int)]
def Km(d, x, y): return [s for s in Vm(d, x, y) if isinstance(s, int)]
def Ka(d, x, y): return sorted(set(Kx(d, y)+Ky(d, x)+Km(d, x, y)))

def chk(d):
	N = list(xrange(1, 10))
	for i in xrange(9):
		if sorted(Vx(d,i))!=N: return False
		if sorted(Vy(d,i))!=N: return False
	for x,y in Lm3():
		if sorted(Vm(d,x,y))!=N: return False
	return True

def f(sIn):
	pIn = open(sIn)
	L = pIn.read().split("\n")
	c = 0
	for i in xrange(0, len(L), 10):
		print "-------\n",L[i]
		d = {}
		for x,y in Lxy(): d[x,y] = int(L[i+y+1][x])
		d = d0(d)
		while not chk(d):
			dd = d.copy()
			for i in xrange(1, 6):
				for x, y in Lt(d):
					if len(d[x,y])==1: d[x,y] = d[x,y][0]
				d = eval("d"+str(i)+"(d)")
				if dd!=d: break
				
		for y in xrange(9): print y,[d[(x,y)] for x in xrange(9)]
		c += d[0,0]*100+d[1,0]*10+d[2,0]
		
	pIn.close()
	pIn = None
	return c
	
if __name__=="__main__":
	import os, sys
	sIn = os.path.join(os.path.dirname(__file__), "problem096_sudoku.txt")
	s = f(sIn)
	print s
数独を辞書dに格納します。
辞書dのキーは(0,0)-(8,8)の81マスの位置を示す座標タプルで、
値は候補値の配列(リスト型)、または確定値(整数型)です。

1.関数f(sIn)
・問題ファイルのパスを受け取り、リストLに各行を要素として全部読み込みます。
・リストLをヘッダを含めた10行ごとに取り出し、
 左上(0,0)-右下(8,8)の81マスの位置を示す座標タプルをキーとする辞書d
 に、値を1つずつ格納しておきます。
・最初に関数d0()で未設定値0を候補リストまたは確定値に変換しておきます。
・次に、関数chk()で数独辞書dが完成したかチェックOKになるまで、関数d1()から関数d5()を順に行います。
 ただし、関数d1()から関数d5()はこの順に基本的な操作から順に並べているので、マスの値が1つでも変化した時点で、改めて関数d1()から再処理します。
・数独が完成したら、左上3マスの値を3桁の数値にして、変数cに累積します。

2.関数d0():
・数独辞書dの未設定値0のマスに[1~9]を候補リストとして設定します。

3.関数d1()
・数独全体の候補リストについて、候補リストから関係項目に存在する確定値を削除します。












4.関数d2()
・行内、列内、3x3メッシュ内もそれぞれで、その全候補値の中に1つだけ存在する値があれば確定値とします。











5.関数d3()
・3x3メッシュ内に2つだけある候補値の位置が一列に並んでいるとき、
どちらかが確定値(例の○か◎)なので、
その並んでいる行内、または列内の他の候補からその値(例の×)を削除します。






















6.関数d4()
・ある候補値が2つだけ存在する行が2つあり、しかもその列位置が同じ場合、
 これら4つの値のうち、いずれかの対角位置の候補値が確定値(例の○か◎)になるため、この2つの列内の候補リストから、この候補値(例の×)を削除します。
 列についても上記と同様に処理します。





















7.関数d5()
・行内に、候補値が2つだけの同一の候補リストが2つのマスにある場合、
どちらの候補値もどちらかのマスのそれぞれの確定値(下図の○か◎)になるため、このマスを含む、列内、また3x3メッシュがあればそれも含めた候補リストからこの2つの値(例の×)を削除します。
列内も同様です。















8.その他
部品となる関数を以下の通りです。
・Lxy():全マスの項目番号リスト
・Lm(x, y):数独辞書dのx列y行が含まれる3x3メッシュの項目番号リスト
・Lm3()   :3x3メッシュの左上マスの項目番号リスト

・Lt(d)   :数独辞書dの処理中の項目番号リスト
・Ltx(d,y):数独辞書dのy行目の処理中の項目番号リスト(横方向)
・Lty(d,x) :独辞書dのx列目の処理中の項目番号リスト(縦方向)
・Ltm(d,x,y):数独辞書dのx列y行が含まれる3x3メッシュの処理中の項目番号リスト

・Vx(d, y)  :数独辞書dのy行目の値リスト(横方向)
・Vy(d, x)  :数独辞書dのx列目の値リスト(縦方向)
・Vm(d, x, y):数独辞書dのx列y行目が含まれる3x3メッシュの値リスト
・Va(d, x, y):数独辞書dのx列y行に関係する値リスト

・Vtx(d, y)  :数独辞書dのy行目の値リスト(横方向)
・Vty(d, x)  :数独辞書dのx列目の値リスト(縦方向)
・Vtm(d, x, y):数独辞書dのx列y行目が含まれる3x3メッシュの値リスト

・Kx(d, y) :数独辞書dのy行目の確定値リスト(横方向)
・Ky(d, x) :数独辞書dのx列目の確定値リスト(縦方向)
・Km(d, x, y):数独辞書dのx列y行目が含まれる3x3メッシュの確定値リスト
・Ka(d, x, y):数独辞書dのx列y行に関係する確定値リスト

・chk(d):#数独辞書dの縦、横、3x3メッシュの全てが[1~9]であるかの最終チェック


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
24702

2014年8月20日水曜日

プロジェクトオイラー 問95

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問95「友愛鎖」
ある数の真の約数とは, それ自身を除く約数すべてである. 例えば, 28 の真の約数は 1, 2, 4, 7, 14 である. これらの約数の和は 28 に等しいため, これを完全数と呼ぶ.
面白いことに, 220 の真の約数の和は 284 で, 284 の真の約数の和は 220 となっており, 二つの数が鎖をなしている. このため, 220 と 284 は友愛数と呼ばれる.

さらに長い鎖はあまり知られていないだろう. 例えば, 12496 から始めると, 5 つの数の鎖をなす.
12496 → 14288 → 15472 → 14536 → 14264 (→ 12496 → ...)
この鎖は出発点に戻っているため, 友愛鎖と呼ばれる.

いずれの要素も 1,000,000 を超えない最長の友愛鎖の最小のメンバーを求めよ.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。
def d(n):
	c, m = 1, int(n**.5)
	for i in xrange(2, m+1):
		if not n%i:
<			c += i
<			j = n//i
<			if i<j: c += j
	return c

def f(n):
	L, N = [d(i) for i in xrange(n+1)], []
	for i in xrange(n+1):
		j, M = L[i], []
		while (j not in M) and (j<=n):
<			M.append(j)
<			j = L[j]
		if M and i==M[-1] and len(N)<len(M): N = [i]+M[:-1]
	return len(N), min(N), N

n = 1000000
n,m,L = f(n)
print "min:",m
print "num:",n
print "chain:",L


1.関数d(n)
・関数d(n)はnの友愛数を返します。問21を改良しました。
・数値nの「真の約数」の上限は、その定義からnの正の平方根です。
 n**(0.5) が、0.5乗ということで、nの正の平方根です。
・変数cに真の約数を累積していきます。
 //演算は割り算の商(整数)で、%演算は割り算の余りです。
 変数iのforループで、nがiで割り切れるとき、変数cにiを累積します。
 割り切れるときは余り0で、%演算の結果がFalseになるので、not演算子でこのような場合を捉えます。
 またこのときの商j=n//iも約数なので、i<jならこれも変数cに累計します。
 nが平方数でiがその平方根の場合、i=jなのでiだけ累積しjは累積しません。

2.関数f(n)
・関数f(n)は、n以下の友愛数リストを取得し、その友愛数をたどり、最長の友愛鎖を返します。
・リストLは、その位置のオフセット値の友愛数を持つリストです。例)L[220]=284
・リストNには、最長の友愛鎖を格納します。
・ループ変数iは、友愛鎖をたどる最初の数値です。
・変数jは次の友愛数が格納されます。
・リストMには、数値iから始まる友愛鎖を格納します。
・while文のループ条件は、
 友愛数jが友愛鎖リストMになく、上限値以下であることとしています。
・友愛鎖リストMを最長友愛鎖リストNに保存する条件は、
 リストMに値があり、友愛鎖たどりの最初の数値iが友愛鎖の最後の値と一致し、
 リストNよりも長い(最長の友愛鎖)こととしています。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
min: 14316
num: 28
chain: [19116, 31704, 47616, 83328, 177792, 295488, 629072, 589786, 294896, 358336, 418904, 366556, 274924, 275444, 243760, 376736, 381028, 285778, 152990, 122410, 97946, 48976, 45946, 22976, 22744, 19916, 17716, 14316]

2014年8月12日火曜日

プロジェクトオイラー 問94

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索です。

問94「擬正三角形」
一辺の長さが整数の正三角形は面積が整数にならないことを示すのは簡単である. 
しかし, 5-5-6の辺を持つ殆ど正三角形に近い擬正三角形 (almost equilateral triangle) は面積が12で整数である.
以降, 二等辺三角形で, 3つめの辺の長さが他と1つしか違わないもの (5-5-6, 5-5-4等) を, 擬正三角形と呼ぶ.

さて, 周囲の長さが1,000,000,000以下の面積が整数になる擬正三角形を考え, その周囲の長さの総和を求めよ.






注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。






私の回答例は以下の通りです。

def f0(n):
	s, c = n//3, 0
	d = {}; dd = {}
	for i in xrange(1, s):
		ii = i*i
		d[i] = ii; dd[ii] = 0
	for i in xrange(3, s, 2):
		t = i//2; u = d[i]
		if (u-d[t]  ) in dd: c += i*3-1
		if (u-d[t+1]) in dd: c += i*3+1
	return c

n = 10**9
print f0(n)

1.関数f(n)
・関数f(n)は周囲の長さがn以下で面積が整数になる擬正三角形の周囲の長さの和を返します。
・変数sは1辺の最大値、変数cは周囲の長さの累積です。
・擬正三角形の二等辺に挟まれた角からもう1辺に垂線hを下ろすと、
 垂線の足が直角である、同じ面積の直角三角形2つに分割できます。
 (以下、半分直角三角形と呼びます)
 ここで半分直角三角形の3辺は以下のように置きます。
 h:擬正三角形の二等辺で挟まれた角からの垂線。
 a:擬正三角形の二等辺であり、半分直角三角形の斜辺。
 b:hと直角を構成する。擬正三角形の底辺の半分の長さ。

・最初のforループで二乗値の準備をします。
 辞書dには二乗値を格納し、辞書ddには整数二乗値をキーにその正の平方根を格納します。

・2つ目のforループは、ループ変数iが擬正三角形の二等辺の長さです。
 これは半分直角三角形の斜辺でもあり、問75より、3辺が整数の直角三角形は、
> 直角を挟む辺a,bは奇数と偶数の組で、斜辺cは奇数です。
 なので、ループ変数iは3から1辺最大値sまで2飛びで値を増加させます。
・変数tは二等辺の長さを2で割って切り捨てることで、
擬正三角形の3辺がi,i,i-1の場合の半分直角三角形の直角を挟む1辺の長さです。
そして、t+1が擬正三角形の3辺がi,i,i+1の場合の半分直角三角形の直角を挟む1辺の長さになります。

・二乗値辞書dから取得した、上記iとtの二乗値の差は、三平方の定理より
 擬正三角形の垂線hの長さの二乗値と等しく、この値が辞書ddのキーにあれば整数直角三角形なので、擬正三角形の周囲の長さを変数cに累積していきます。

・・・と、ここまできて、1分ルールが守れず長考に入ってしまいました・・・。

def f2(n):
	r, s, t, x, y = 0, 0, 0, 1, 0
	while s<=n or t<=n:
		x, y = 2*x+3*y, x+2*y
		
		s = x*x*4
		if s<=n: r += s
		
		t = x+3*y
		t = 2*t*t
		if t<=n: r += t
		
	return r

n = 10**9
print f2(n)

改めて、問75より、3辺が整数の直角三角形では、整数i,j(0<j<i)を使って、
> 斜辺がi2+j2、直角を挟む2辺はi2-j2, 2ij

よって、上記の半分直角三角形に当てはめると、
a = i2+j2 :(A)
(b, h) = (i2-j2, 2ij) or (2ij, i2-j2) :(B)
擬正三角形の周長L = 2(a+b) :(C)
擬正三角形の定義から、a = 2b±1 :(D)

・ケース1:b = i2-j2の場合
(D)に(A)と上記を代入して
i2 + j2 = 2(i2 - j2)±1
i2 + j2 = 2i2 - 2j2 ±1
i2 - 3j2 = ±1 :(E)
また、(C)に(A)と上記を代入して
L = 2((i2+j2)+(i2-j2))
  = 4i2 :(F)

・ケース2:次に上記(B)で、b = 2ijの場合
(D)に(A)と上記を代入して
i2 + j2 = 2・2ij ±1
i2 + j2 = 4ij ±1
(i - 2j)2 - 3j2 = ±1 :(G)
また、(C)に(A)と上記を代入して
L = 2(i2+j2 +2ij)
  = 2(i+j)2 :(H)
ここで、t=i-2jと置くと、(G)、(H)は、
t2 - 3j2 = ±1 :(G')
L = 2((t+2j) +j)2
  = 2(t+3j)2 :(H')
 
・上記の2つの場合をまとめると、
x2 - 3y2 = ±1 :(J)
L = 4x2, 2(x+3y)2 :(K)

・さてここで、(J)の「±1」の「-1」の場合を考えてみます。
x2 - 3y2 = -1
x2 = 3y2 -1
xの二乗は、3の倍数に1足りないので、
xの二乗を3で割ると2余ることになります。
x = 3m, 3m+1, 3m+2 として確認していきます。
x = 3mの場合、x2 = (3m)2 = 6m、3で割ると余り0
x = 3m+1の場合、x2 = (3m+1)2 = 9m2+6m+1、3で割ると余り1
x = 3m+2の場合、x2 = (3m+2)2 = 9m2+12m+4、3で割ると余り1
上記より、二乗して3で割ったときに2余る整数は存在せず、式(J)は以下に改めます。
x2 - 3y2 = 1 :(J')

式(j')はペル方程式と呼ばれる式で、以下の公式があります。
x2 - Dy2 = 1 (Dは平方数以外。平方数の場合、整数解が存在しない)
の解は、最小整数解を(x0, y0)、それ以降、(x1, y1),(x2, y2), ...として、
┌xn+1 = x0xn + y0Dyn
└yn+1 = y0xn + x0yn

当問題に当てはめると、D=3、また、、
問題にある5-5-6の擬正三角形が最小整数解で、このときx0=2, y0=1 なので、
┌xn+1 = 2*xn + 3*yn
└yn+1 = 1*xn + 2*yn

上記式をループ内に実装し、上記(K)の擬正三角形の周の長さL = 4x2, 2(x+3y)2 を計算し累積します。




解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
518408346