Google Code Jam Japan 2011 予選

結果

A small,B Small,C Small,C Largeの4つでスコア28点で順位は343位でした。
Smallが5点でLargeが13点です。

Rank Score Time A Small A Large B Small B Large C Small C Large
343 28 2:48:25 40:52 --- 2:48:25 --- 1:15:45 2:05:41


問題A. カードシャッフル

カードがM枚あり、それぞれのカードに1..Mの数字が書かれています。
カードの山をC回カットし、i回目のカットではカードの山の上からAi番目からBi枚取ってそれを残ったカードの山の上に置くようです。
C回のカットが終わったときカードの山の上からW番目のカードの数字を求める問題です。

Small 解法

愚直にシュミレーションします。配列の添字には気をつけましょう。

ソースコード

#include <iostream>
using namespace std;

int main(){
	int t;

	cin >> t;
	for(int i_=0 ; i_<t ; i_++ ){
		int m,c,w,a,b;
		int h[101],temp[101];
		for(int i=0 ; i<101 ; i++ ) h[i] = i+1;
		
		cin >> m >> c >> w;
		for(int i=0 ; i<c ; i++ ){
			cin >> a >> b;

			for(int j=0 ; j <= a-2 ; j++ ){
				temp[j+b] = h[j];
			}
			for(int j=a-1 ; j <= a+b-2 ; j++ ){
				temp[j-a+1] = h[j];
			}
			for(int j=a+b-1 ; j < m ; j++ ){
				temp[j] = h[j];
			}
			for(int j=0 ; j < m ; j++ ){
				h[j] = temp[j];
			}
		}
		cout << "Case #" << (i_+1) << ": " << h[w-1] << endl; 
	}
}


Large 解法

Largeではカードの枚数が多いのでSmallの解法は使えません。
逆順にシュミレーションします。最後の状態のW番目だけみてシュミレーションするので1回のカットでカードの移動が1枚になるのがとても嬉しいですね。

ソースコード

#include <iostream>
using namespace std;

int main(){
	int t;

	cin >> t;
	for(int i_=0 ; i_<t ; i_++ ){
		int m,c,w;
		int a[101], b[101];
				
		cin >> m >> c >> w;
		for(int i=0 ; i<c ; i++ ){
			cin >> a[i] >> b[i];
		}
		for(int i=c-1 ; i >= 0 ; i-- ){
			if( w <= b[i] )
				w = a[i]-1+w;
			else if( w <= a[i]+b[i]-1 )
				w = w-b[i];
		}
		cout << "Case #" << (i_+1) << ": " << w << endl; 
	}
}


問題B. 最高のコーヒー

N種類のコーヒーがあり、i番目のコーヒーはCi杯ありTi日目に消費期限を迎え飲むとSiの満足度が得られる。
1日に1杯だけ消費期限の切れていないコーヒーを飲むことができる。
K日目までに得られる最大の満足度を求める。

Small 解法

N≦8,K≦8なので全探索します。
幅優先探索でも深さ優先探索でもどちらでも問題ないと思います。
ソースコード深さ優先探索で調べています。

ソースコード

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int n,k,ans;

void solve(int d, int p, vector<int> c, vector<int> t, vector<int> s){
	if( d == k ){
		ans = max( ans , p );
		return;
	}
	bool flag = true;
	for(int i=0 ; i < n ; i++ ){
		if( d < t[i] && c[i] > 0 ){
			flag = false;
			c[i]--;
			solve( d+1 , p+s[i] , c , t , s );
			c[i]++;
		}
	}
	if( flag )
		solve( d+1 , p , c , t , s );
}

int main(){
	int t;

	cin >> t;
	for(int i_=0 ; i_<t ; i_++ ){
		int c_,t_,s_;
		vector<int> c,t,s;

		cin >> n >> k;
		for(int i=0 ; i < n ; i++ ){
			cin >> c_ >> t_ >> s_;
			c.push_back( c_ );
			t.push_back( t_ );
			s.push_back( s_ );
		}
		ans = 0;
		solve( 0 , 0 , c , t , s );
		cout << "Case #" << (i_+1) << ": " << ans << endl; 
	}
}


Large 解法

K日目から逆にたどって貪欲法で調べていくらしいです。
うまくコードが書けなかったのでソースコードは割愛します。


問題C. ビット数

f(x)を「xを2進数表記した時の"1"の個数を返す関数」として
正の整数Nが与えられるので、a+b=Nを満たす自然数a,bの組の中でf(a)+f(b)が最大のものを求める。

Small 解法

N≦10000なので全探索します。(a,b)の組は高々(N/2+1)通りしかありません。

ソースコード

#include <iostream>
#include <algorithm>
using namespace std;

int f(int bits){
	int n = 0;
	for( ; bits ; bits &= bits-1 ) n++;
	return n;
}

int main(){
	int t;

	cin >> t;
	for(int i_=0 ; i_<t ; i_++ ){
		int n, ans=0;
		cin >> n;
		for(int a=0 ; a <= n/2 ; a++ ){
			ans = max( ans , f(a)+f(n-a) );
		}
		cout << "Case #" << (i_+1) << ": " << ans << endl; 
	}
}


Large 解法

N≦10^18なので32bit整数では収まらないので気をつける必要があります。
64bit整数(long long int)を使いましょう。
Nを超えない(最大の2のべき乗-1)をaとするとf(a)+f(N-a)が解となります。

ソースコード

#include <iostream>
#include <algorithm>
using namespace std;

unsigned long long int f(unsigned long long int bits){
	unsigned long long int n = 0LL;
	for( ; bits ; bits &= bits-1LL ) n++;
	return n;
}

unsigned long long int g(unsigned long long int n){
	unsigned long long int i = 0xffffffffffffffffLL;
	while( n < i ) i >>= 1;
	return i;
}

int main(){
	int t;

	cin >> t;
	for(int i_=0 ; i_<t ; i_++ ){
		unsigned long long int n,ans=0;
		cin >> n;
		unsigned long long int a = g(n);
		cout << "Case #" << (i_+1) << ": " << ( f(a)+f(n-a) ) << endl; 
	}
}


感想

予選だけは通りたかったのでCはかなり念入りにテストしました。
とりあえず予選通ってたのでよかったです。
全完できなかったし決勝はちょっとつらいかもしれない。
Tシャツほしいなー。