kmjp's blog

競技プログラミング参加記です

yukicoder : No.344 ある無理数の累乗

とても思い入れのある問題。
http://yukicoder.me/problems/857

問題

整数Nに対し \lfloor (1+\sqrt{3})^N \rfloor \mod 1000を求めよ。

解法

類題がGCJで既出です。(3+√5)^Nの整数部分を答える問題。蟻本にも載っている有名問題ですね。
https://code.google.com/codejam/contest/32016/dashboard#s=a&a=2

Nが0の時は自明なので、Nが1以上の時を考える。
 f(n) = (1+\sqrt{3})^n + (1-\sqrt{3})^nと置く。
整数a_n, b_nを用いて (1+\sqrt{3})^n = a_n + b_n*\sqrt{3}とおくと、二項定理より (1-\sqrt{3})^n = a_n - b_n*\sqrt{3}と書け、うまく√3の項が消えて f(n) = 2*a_nとなり整数となる。

f(n)の後半 (1-\sqrt{3})^nの部分はnが奇数なら-1~0の間の小数、nが偶数なら0~1の間の小数となる。
よって求めたい値はnが奇数ならf(n)、偶数ならf(n)-1となる。

さて、a_n,b_nは (1+\sqrt{3})^{n+1} = (a_n + \sqrt{3}*b_n)(1+\sqrt{3}) = (a_n + 3b_n) + (a_n + b_n)*\sqrt{3}より
 a_{n+1} = a_n + 3b_n, b_{n+1} = a_n + b_nの関係にあることがわかる。
あとは行列累乗テクでa_Nを求めよう。

ll N;
ll mo=1000;

const int MAT=2;
struct Mat { ll v[MAT][MAT]; };
Mat mulmat(Mat& a,Mat& b,int n=MAT) {
	int x,y,z; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(x,n) FOR(z,n) FOR(y,n) r.v[x][y] += (a.v[x][z]*b.v[z][y]) % mo;
	FOR(x,n) FOR(y,n) r.v[x][y]%=mo;
	return r;
}

Mat powmat(ll p,Mat a,int n=MAT) {
	int i,x,y; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(i,n) r.v[i][i]=1;
	while(p) {
		if(p%2) r=mulmat(r,a,n);
		a=mulmat(a,a,n);
		p>>=1;
	}
	return r;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	if(N==0) return _P("1\n");
	
	Mat m;
	m.v[0][0]=1;
	m.v[0][1]=3;
	m.v[1][0]=1;
	m.v[1][1]=1;
	auto m2=powmat(N,m);
	
	cout<<(2*m2.v[0][0]+((N%2)?0:999))%1000<<endl;
}

まとめ

以下の記事を見てGCJ2009に参加したのが初プロコンなので、非常に思い入れのある問題でした。
Google Japan Blog: Google Code Jam 2009 を開催します