「オイラープロジェクト141~150」の編集履歴(バックアップ)一覧に戻る

オイラープロジェクト141~150 - (2012/11/27 (火) 17:19:00) のソース

*問い142
Problem 142 「完全平方数コレクション」 †
x + y, x - y, x + z, x - z, y + z, y - zが全て平方数となる整数の組 x > y > z > 0で, 最小の x + y + z を求めよ.

*解法
答えが100万近辺とか小さな数であることを祈って解いた問題。
他の人の解法を見ると私ひとり頭の悪い解法を取ってるらしい。
この問題はメモ化とかより式変形が大事だったようだ。
y=z+a1
x=z+a2とすると
x-y=a2-a1=平方数となるのでこの条件を満たすa1とa2の組み合わせを最初の2重ループで求めます。
後はこの組み合わせとzの組み合わせを全て試す2重ループで試しただけです。
zは式変形するとa1,a2の値から増加量が決まるのでzの増加数を適切にとることで計算量を抑えます。
これで約1秒で答えが出ます。



 #include<stdio.h>
 #include<vector>
 #include<set>
 struct okSet{
	int a1,a2,b1;
 };
 int main(){
	std::set<int> memo;
	std::vector<int> nums2;
	std::vector<okSet> oks;
	int t,up=1000,x,y,z;
	
	okSet ok;
	for(int i=1;i<up;i++){
		memo.insert(i*i);
		nums2.push_back(i*i);
		for(int j=0;j<nums2.size()-1;j++){
			t=nums2[i-1]-nums2[j];
			if(memo.find(t)!=memo.end()){
				ok.a2=nums2[i-1];
				ok.a1=nums2[j];
				ok.b1=2*(i+1);
				oks.push_back(ok);
			}
		}
	}
	printf("%d\n",oks.size());
	__int64 ans=0;
	for(int i=0;i<oks.size();i++){
		int add=oks[i].b1;
		for(z=oks[i].b1;z<up*1000;z+=add){
			x=z+oks[i].a2;
			y=z+oks[i].a1;
			add+=4;
			if(x+y+z>ans&&ans!=0)break;
			if(memo.find(x+y)==memo.end())continue;
			if(memo.find(x+z)==memo.end())continue;
			if(memo.find(y+z)==memo.end())continue;
			if(ans==0||ans>x+y+z)ans=x+y+z;
			printf("(%d %d %d)",x,y,z);
			break;
		}
	}
	printf("\nans=%d",ans);
 }











*問い145
http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%20145



解法
特定の数字の倍数のみしかこの条件を満たさないみたいですが、探索範囲が十億までの数と狭いので全部試してみました。

 #include<stdio.h>
 bool okKisuu(int a){
	while(a!=0){
		if((a%10)%2==0)return false;
		a/=10;
	}
	return true;
 } 
 int rev(int a){
	int re=0;
	while(a!=0){
		re=re*10+a%10;
		a/=10;
	}
	return re;
 }
 int main(){
	int ans=0;
	for(int a=1;a<10000*10000*10;a++){
		if(a%10==0)continue;
		ans+=okKisuu(rev(a)+a);
	}
	printf("%d",ans);
 }














*問い149
下の表において、任意の方向(縦横斜め)に隣り合うものの和の最大値は16 (= 8 + 7 + 1)となることは簡単に確かめられる。
 -2	5	3	2
 9	-6	5	1
 3	2	7	3
 -1	8	-4	8
いま、同じ探索をより大きなものについてもしてみることにする。
まず、400万個の擬似乱数を"Lagged Fibonacci Generator"によって生成する。
1 ≤ k ≤ 55 について、sk = [100003 - 200003k + 300007k3] (modulo 1000000) - 500000 
56 ≤ k ≤ 4000000 について、sk = [sk-24 + sk-55 + 1000000] (modulo 1000000) - 500000
つまり、s10 = -393027 , s100 = 86613 となる。
s の項は、最初の2000個を最初の行に(順番に)、次の2000個を2番目の行に、というように、2000x2000の表に並べ替えられる。
任意の方向(縦横斜め)に隣り合うものの和の最大値を求めよ。 (連続して足す領域は3マス以上でもよい、斜め4マス等も認める)



*解法
速度とメモリを使わない方向で頑張ってみた問題。
縦横斜めで分解して一段ずつの動的計画法で正答。
そこそこ速度は出てるはず。
計算量は大雑把に言って400万*4回。


 #include<stdio.h>
 #include<string.h>
 #include<algorithm>
 int main(){
	int lines[4001],memoLR[2002],memoUD[2002],memoRU[2002],memoLU[2002],nextRU[2002];
	__int64 temp;
	__int64 mod=1000000,del=500000;
	int ans=-mod*10;
	for(int k=1;k<=55;k++){
		lines[k]=(((__int64)(k*k*k))*300007-200003*k+100003)%mod-del;
		memoLR[k]=memoUD[k]=memoRU[k]=memoLU[k]=lines[k];
	}
	for(int k=56;k<=2000;k++){
		lines[k]=(lines[k-24]+lines[k-55]+mod)%mod-del;
		memoLR[k]=memoUD[k]=memoRU[k]=memoLU[k]=lines[k];
	}
	for(int r=2;r<=2000;r++){
		for(int k=2001;k<=4000;k++){
			lines[k]=(lines[k-24]+lines[k-55]+mod)%mod-del;
			int p=k-2000;
			int val=lines[k];
			if(p==1){
				memoLU[1]=memoRU[1]=memoLR[1]=val;
			}else if(p==2000){
				memoLR[p]=std::max(memoLR[p-1]+val,val);
				memoLU[p]=std::max(memoLU[p-1]+val,val);
				memoRU[p]=val;
			}else{
				memoLR[p]=std::max(memoLR[p-1]+val,val);
				memoLU[p]=std::max(memoLU[p-1]+val,val);
				nextRU[p]=std::max(memoRU[p+1]+val,val);
			}
			memoUD[p]=std::max(memoUD[p]+lines[k],lines[k]);
			ans=std::max(memoUD[p],ans);
			ans=std::max(memoLU[p],ans);
			ans=std::max(nextRU[p],ans);
			ans=std::max(memoLR[p],ans);
		}
		memcpy(&memoRU[1],&nextRU[1],sizeof(int)*2000);
		memmove(&lines[1],&lines[2001],sizeof(int)*2000);
	}
	printf("%d",ans);
 }



*問い150
http://projecteuler.net/problem=150


理論値 計算量O(1000^3)、メモリは__int64で100万個と少しばかりの配列を消費。
少しばかりめんどくさかったがまあ動的計画法としては単純。


 #include<stdio.h>
 #include<iostream>
 #include<string.h>
 __int64 memo[1002][1002],sum[1001]={0};
 int main(){
 	__int64 ans=273519,s,t=0,m20=(1<<20),m19=(1<<19),d;
	memset(memo,0,sizeof(memo));
 	for(int r=1;r<=1000;r++){
 		memset(sum,0,sizeof(sum));
 		for(int c=1;c<=r;c++){
			t=(615949*t+797807)%m20;
 			sum[c]=sum[c-1]+t-m19;
 		}
 		for(int c=r;c>0;c--){
 			for(int c2=0;c2<c;c2++){
 				d=sum[c]-sum[c2];
 				memo[c][c-c2]=d+memo[c-1][c-c2-1];
 				if(ans>memo[c][c-c2])ans=memo[c][c-c2];
 			}
 		}
 	}
 	std::cout<<ans<<"\n";
 }