TopCoder Nisoku(SRM463 DIV1 Middle, DIV2 Hard)

No comments 22 3月 2010 Under: DIV 1 Middle, DIV 2 Hard, TopCoder

問題概説

1.5〜10.0までの数値が書かれたカードの山があります。この中から2枚取り出し、それぞれの数値をaとbとします。新しいカードにa+bあるいはa*bを記入して山に戻します。

問題

最後に残ったカードの数値で最大のものを返して下さい。

方針1

大きい順にソートして、小さい方から2枚抜き、a+bとa*bで大きいほうを戻すという作業を繰り返す。

	double theMax(vector<double> cards)
	{
		sort(cards.begin(), cards.end(), less_() );

		while( cards.size() != 1 )
		{
			double a = cards[cards.size()-1];
			double b = cards[cards.size()-2];

			cards.pop_back();
			cards.pop_back();

			if( a + b > a * b )
				cards.push_back( a + b );
			else
				cards.push_back( a * b );

			sort(cards.begin(), cards.end(), less_() );
		}

		return cards[0];
	}

{1.5, 1.7, 1.6, 1.5}
Returns: 9.920000000000002

これが通らない。

電卓で検算すると(1.5+1.7)*(1.5+1.6)で9.92となることがわかる。

方針2

足し算は大きい数値と小さい数値を足したほうがいいらしい。足した方がいいのはx^2 < 2xを解いてx <= 2(ただし0 < x)のときなので、2より大きいか小さいかでカードをまず2つの山に分ける。

小さいカードの山の一番小さいカードと大きいカードを引いて、足したものを2以上の山に入れる。残ったらそのまま2以上の山に入れる。掛け算は順番を変えても同じなので、あとは山のカードを全部かける。

	double theMax1(vector<double> cards)
	{
		vector<double> lessThan2;
		vector<double> biggerThan2;

		sort(cards.begin(), cards.end(), less_() );

		// 2より大きいか小さいかで山を分ける
		while( cards.empty() != true )
		{
			if( cards[cards.size() - 1] < 2 )
			{
				lessThan2.push_back(cards[cards.size() - 1]);
				cards.pop_back();
			}
			else
			{
				biggerThan2 = cards;
				break;
			}
		}

		// 2より小さいカードの山の上と下を足して2より大きい山に戻す
		while ( lessThan2.size() > 1 )
		{
			double a;
			double b;

			sort(lessThan2.begin(), lessThan2.end(), less_() );

			a = lessThan2[lessThan2.size()-1];
			b = lessThan2[0];

			lessThan2.pop_back();
			lessThan2.erase(lessThan2.begin());

			biggerThan2.push_back( a + b );
		}

		// 2より小さいカードの山に残っていたら(奇数の時)大きい山に入れる
		while( lessThan2.empty() != true )
		{
			biggerThan2.push_back(lessThan2[lessThan2.size()-1]);
			lessThan2.pop_back();
		}

		// かける
		for( int i = 0; i < biggerThan2.size(); i++ )
		{
			result *= biggerThan2[i];
		}

		return result;
	}

SystemTest

例題は全部通るが、SystemTestで

	void test_case_5()
	{
		double Arr0[] =
		{
			5.80937, 9.81643, 8.75915, 1.79267, 4.73352, 2.9275, 2.9058, 5.4856,
			3.60501, 4.7097, 3.06224, 2.83059, 1.68458, 3.26268, 2.14842, 5.48844,
			3.35253, 6.70429, 6.52142, 2.93687, 1.65934, 6.63675, 2.0463, 4.97966,
			6.0123, 5.34671, 7.11036, 2.90532, 5.48402, 8.18955, 7.64127,
			8.33337, 6.82876, 2.93654, 7.3643
		};
		vector  Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0])));
		double Arg1 = 3.2117967730802522E22; verify_case(5, Arg1, theMax(Arg0));
	}

が通らない。要考察。

方針3

前回は「2より小さいカードの山に残っていたら(奇数の時)大きい山に入れる」処理を入れていた。1.5より大きい数値同士を足すと2を越えるため、小さいカードの山から2枚抜き、大きいカードの山に入れることになる。つまり小さいカードの山は偶数枚でないと余ることになる。そのため、残ったカードをそのまま大きいカードの山に入れることにしたが、これがよくない気がする。

偶数枚ずつ小さいカードの山から抜いていくため、最初から2枚単位で小さいカードの山に入れることにした。また

1.659340, 1.684580, 1.792670,

9.816430, 8.759150, 8.333370, 8.189550, 7.641270, 7.364300, 7.110360, 6.828760, 6.704290, 6.636750, 6.521420, 6.012300, 5.809370, 5.488440, 5.485600, 5.484020, 5.346710, 4.979660, 4.733520, 4.709700, 3.605010, 3.352530, 3.262680, 3.062240, 2.936870, 2.936540, 2.927500, 2.905800, 2.905320, 2.830590, 2.148420, 2.046300,

小さいカードの山と大きいカードの山がこのようになるケースでうまくPASSしない。この場合は小さいカードの山が奇数なので、小さいカードの山で一番大きい1.792670を大きいカードの山に入れるか、逆に大きいカードの山で一番小さい2.046300を小さいカードの山に入れるかのどちらかになる。

そのどちらがよいかは数値によって異なる。つまり、2通りの可能性について計算をして、大きい方を返すことにする。

面倒臭くなったので

  1. 小さい山に0, 2, 4枚....ずつ入れていく
  2. 小さい山の一番小さい数値 + 一番大きな数値を大きな山に入れる
  3. 掛け算をして暫定の結果を出す
  4. 最初に戻り計算し直す、その際に暫定の結果より小さい結果になったら打ち切り

という方針にする。

実装例

	double theMax(vector<double> cards)
	{
		double result = 1.0;
		int i = 0;

		sort(cards.begin(), cards.end() );

		while(true)
		{
			int j = 0;
			double t = 1.0;
			vector<double> small;
			vector<double> big;

			for( j = 0; j < min( (int)cards.size(), i ); j++ )
			{
				small.push_back(cards[j]);
			}
			copy( cards.begin() + j, cards.end(), back_inserter( big ) );

			while( small.size() >= 2 )
			{
				double a = *(small.begin());
				double b = *(small.end() - 1);

				small.pop_back();
				small.erase(small.begin());

				big.push_back( a + b );
			}
			for( int k = 0; k < big.size(); k++ )
			{
				t *= big[k];
			}

			if( result >= t )
				return result;
			else
				result = t;

			i += 2;
		}

		return result;
	}

SystemTest

全部通ることを確認した。

TopCoder RabbitNumbering(SRM463 DIV1 Easy, DIV2 Middle)

No comments 21 3月 2010 Under: DIV 1 Easy, DIV 2 Middle, TopCoder

問題概説

兎がたくさんいます。i番目の兎は1〜maxNumber[i]番の重複しない番号をくれくれ言っています。

問題

何通りの番号の与え方があるか返して下さい(mod 1,000,000,007)。全部の兎の要望を満足できないときは0を返して下さい。

例1

{5}
Returns: 5

1〜5番の番号がつけられます。

例2

{4, 4, 4, 4}
Returns: 24

最初が4通り、次が同じ番号を除いた3通り。同様に4 * 3 * 2 * 1 = 24通り。

例3

{5, 8}
Returns: 35

最初が5通り、次が(8-1)通りで5*7=35通り。

例4

{2, 1, 2}
Returns: 0

真ん中が1しか割り当てられない。そうすると左は2しか割り当てられず、右の兎に割り当てる数値がない。

例5

{25, 489, 76, 98, 704, 98, 768, 39, 697, 8, 56, 74, 36, 95, 87, 2, 968, 4, 920, 54, 873, 90}
Returns: 676780400

全ての組み合わせを計算するが、大きくなるのでmod 1,000,000,007する。

方針・注意点

ソートして順にかけていく。i羽目の兎はmaxNumber[i] – iの可能性がある。( maxNumber[i] – i ) < 0ならば0を返すのを忘れずに。

※ただし、意識しなくても0をかければ結果は0になるので場合分けは不要。

注意点

mod 1000000007はintの上限2147483647の約半分なので、intを使っていると溢れる可能性がある。long longなど大きい変数で計算をする。maxNumber[i]は最大1000なので、1000000007 * 1000が収まる変数ならば大丈夫。

実装例

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

class RabbitNumbering
{
public:
	int theCount(vector<int> maxNumber)
	{
		long long result = 1;

		sort( maxNumber.begin(), maxNumber.end() );

		for( int j = 0; j < maxNumber.size(); j++ )
		{
			int a = maxNumber[j] - j;

			if( a < 0 )
				return 0;

			result *= a;
			result %= 1000000007;
		}

		return (int)result;
	}
};

SystemTest

通ることを確認した。

感想

500点問題だが、250点問題よりも易しいと感じた。resultが32ビット変数の範囲を超えることだけが注意点だが、例5がその可能性を教えてくれる。

TopCoder BunnyPuzzle(SRM463 DIV2 Easy)

No comments 21 3月 2010 Under: DIV 2 Easy, TopCoder

問題概説

兎が一直線上にいます。vector bunniesに初期状態の座標が与えられています。

兎A(座標a), B(座標b)を選びます。兎Aは兎Bを飛び越え、座標2*b-aに着地します。もし座標2*b-aに既に兎がいたら跳び越えることはできません。また、二羽の兎を飛び越えることはできません。

問題

何通りの飛び越え方があるか返して下さい。

例1

{5, 8}
Returns: 2

2通りの飛び越え方があります。

  1. 左の兎(座標5)が右の兎(座標8)を飛び越えて座標11( 8 * 2 – 5 )に着地します
  2. 右の兎(座標8)が左の兎(座標5)を飛び越えて座標2( 5 * 2 – 8 )に着地します

例2

{-1, 0, 1}
Returns: 2

2通りの飛び越え方があります。

  1. 中央の兎(座標0)が右の兎(座標1)を飛び越えて座標2に着地します
  2. 中央の兎(座標0)が左の兎(座標-1)を飛び越えて座標-2に着地します

既に兎がいるところには着地できないため

  1. 左の兎(座標-1)が中央の兎(座標0)を飛び越えて、座標1には着地できません
  2. 右の兎(座標1)が中央の兎(座標0)を飛び越えて、座標-1には着地できません

方針

全ての兎についてループを回し、左右に飛べるかを確認します。注意すべき点はメモリの範囲外にアクセスしないこと。

ソースコード

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

class BunnyPuzzle
{
public:
	int theCount(vector<int> bunnies)
	{
		int result = 0;
		int size = bunnies.size();

		for( int i = 0; i < size; i++ )
		{
			// left OK?
			if( i != 0 )
			{
				int a = bunnies[i];
				int b = bunnies[i-1];
				int c = 2*b-a;

				if( 0 > i - 2 || bunnies[i-2] < c )
					result++;
			}

			// right OK?
			if( i + 1 < size )
			{
				int a = bunnies[i];
				int b = bunnies[i+1];
				int c = 2*b-a;

				// 右に何もない or i+2がcより大きい
				if( i + 2 >= size || c < bunnies[i+2] )
					result++;
			}
		}

		return result;
	}
};

TopCoder ColorfulMazeTwo(SRM464 DIV2 Hard)

問題概説

string[] mazeで与えられたカラフルな迷路にいます。i番目の要素のj文字目が迷路の( i, j )座標に相当します。迷路には次のタイプがあります。

  • # : 壁、通れない
  • . : 通路、自由に通れる
  • A-G : 色のついた通路、自由に通れる
  • $ : 入り口
  • ! : 出口

色のついた床は罠のある危険な床ですが、あなたは事前にどの色の床が危険であるか知りません。あなたの知っていることは、どの色が危険かという確率だけです。int[] trapが与えられていて、最初の要素はAが危険である確率で、以下同様です。罠の仕掛けられている床を踏むとダメージを受けます(訳注:罠にかかると死んでしまうようで失敗となります)。

迷路は四方に動くことができますが、斜めには動くことはできません。

問題

ダメージを受けないで迷路を脱出できる最大の確率を求めて下さい。

例1

{ ".$.",
  "A#B",
  "A#B",
  ".!." }
{ 50, 50, 0, 0, 0, 0, 0 }
Returns: 0.5

仮に左回りを選んだとします。Aに罠が仕掛けられている確率は50%です。もし罠が仕掛けられていていたら、Aを通過すると死んでしまいます。もし罠がなければ次のAも安全です。したがって左回りで生存する確率は50%です。右回りも同様に50%です。

例2

{ "$A#",
  ".#.",
  "#B!" }
{ 10, 10, 10, 10, 10, 10, 10 }
Returns: 0.0

どのように歩いても出口に到達できないため、確率は0です。

例3

{ "$A..",
  "##.A",
  "..B!" }
{ 50, 50, 0, 0, 0, 0, 0 }
Returns: 0.5

Aを2回通るだけなので50%です。Bは通る必要はありません。

例4

{ "$C..",
  "##.A",
  "..B!" }
{ 50, 50, 100, 0, 0, 0, 0 }
Returns: 0.0

Cを必ず通り、Cには罠が100%仕掛けられているので生存確率は0%です。

例4

{ ".$.D.E.F.G.!." }
{ 10, 20, 30, 40, 50, 60, 70 }
Returns: 0.036000000000000004

D, E, F, Gを踏みますので、( 1 – 0.7 ) * ( 1 – 0.6 ) * ( 1 – 0.5 ) * ( 1 – 0.4 ) = 0.3 * 0.4 * 0.5 * 0.6 = 0.036です。

方針1:BFS(SystemTestで撃沈)

幅優先探索(BFS)で探索します。

ルートの記録

現在地、今までに通った経路(フラグ)、生存確率、トラップの発動確率(一度踏んだ色は0にしておく)を持ったlocation構造体を用意します。

初期化

location構造体を1つ用意して、スタート地点、トラップの発動確率(問題から与えられる)、現時点での生存率(初期値は1.00)を設定してキューにプッシュします。

whileループを回します。脱出条件はキューが空(もう探索すべき点がない)こと。

ループ内ですること

まずキューから1つlocationを取り出します。locationがゴールならこれ以上探索は要らないので、キューに何も追加せずcontinueします。その際に、今まで歩いてきたルートでの生存確率と、今までにチェックした最大の生存確率と比較して大きい方を残します。

上下左右の探索

上下左右をチェックして、壁ではない、範囲外ではない、今までに踏んでいないときは探索します。幅優先なので、新しいポイントをキューにプッシュして別の可能性を考えます。

実装例

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

struct location
{
	int x;
	int y;
	double p;
	bool flag[50][50];
	vector <int> trap;
};

class ColorfulMazeTwo {
private:
	vector <string> map;
	int width;
	int height;

public:

	bool check( location &l )
	{
		if( l.x < 0 ) return false;
		if( l.y < 0 ) return false;
		if( l.x >= width ) return false;
		if( l.y >= height ) return false;

		if( map[l.y][l.x] == '#' ) return false;
		if( l.flag[l.y][l.x] == true ) return false;

		return true;
	}

	double getProbability(vector <string> maze, vector <int> trap)
	{
		double result = 0.0;
		map = maze;
		queue<location> que;
		location l;
		width = maze[0].length();
		height = maze.size();

		printf("width = %d\n",width);
		printf("height = %d\n",height);

		for( int i = 0; i < height; i++ )
		{
			int s = maze[i].find('$');
			if( s == maze[i].npos ) continue;

			l.x = s;
			l.y = i;
			l.p = 1.00;
			l.trap = trap;

			for( int x = 0; x < width; x++ )
				for( int y = 0; y < height; y++ )
					l.flag[y][x] = false;
			que.push(l);
			break;
		}

		while(!que.empty())
		{
			location newLocation;
			l = que.front();
			que.pop();

			printf( "キューから(%d,%d)を取り出しました\n", l.x, l.y );

			// 現在地の確認
			char floor = map[l.y][l.x];
			if( 'A' <= floor && floor <= 'G' )
			{
				int kind = floor - 'A';
				double probability_of_survive = 1.00 - (l.trap[kind]/100.0);
				l.p *= probability_of_survive;
				l.trap[kind] = 0;

				cout << floor << "を踏みました:生存確率" << l.p << endl;
			}

			// exit
			if( floor == '!' )
			{
				if( result < l.p )
					result = l.p;

				puts("ゴールに到達しました");
				printf( "Location(%d,%d)\n", l.x, l.y );				

				continue;
			}

			// can move left
			newLocation = l;
			newLocation.x--;
			if( check(newLocation) )
			{
				newLocation.flag[l.y][l.x] = true;
				printf("(%d,%d)をキューに入れました\n", newLocation.x, newLocation.y );
				que.push(newLocation);
			}

			// can move right
			newLocation = l;
			newLocation.x++;
			if( check(newLocation) )
			{
				newLocation.flag[l.y][l.x] = true;
				printf("(%d,%d)をキューに入れました\n", newLocation.x, newLocation.y );
				que.push(newLocation);
			}

			// can move up
			newLocation = l;
			newLocation.y--;
			if( check(newLocation) )
			{
				newLocation.flag[l.y][l.x] = true;
				printf("(%d,%d)をキューに入れました\n", newLocation.x, newLocation.y );
				que.push(newLocation);
			}

			// can move up
			newLocation = l;
			newLocation.y++;
			if( check(newLocation) )
			{
				newLocation.flag[l.y][l.x] = true;
				printf("(%d,%d)をキューに入れました\n", newLocation.x, newLocation.y );
				que.push(newLocation);
			}
		}

		puts("キューが空になりました。");
		printf("最大値は%lfです\n",result);
		return result;
	}
};

SystemTest

全部で25のテストケースのうち2つ通らないケースがありました。1つは

{"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDD$DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDD!DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"},
{53, 12, 40, 75, 61, 64, 91}

で、もう1つも同様に広い部屋に放り出されるタイプです。こういう問題は幅優先で全域探索すると時間オーバーになってしまいます。

方針1:DFS(書きかけ)

幅優先探索で行き詰まったので、深さ優先探索を考えてみました。

事前に確率を計算しておく

トラップがA〜Gまでの7種類あることから、トラップを踏むパターンは2^7=128通りしかないことになります。したがって、例えばABCを踏むルートの確率は事前に計算できることになります。この確率を計算して、確率の高い順にソートします。

次に確率の高い順から深さ優先探索で到達可能か検証していきます。例えばABCを通る確率が0.8で、これを検証中にDをどうしても通るとしたら失敗になります。これによって探索する空間を小さくします。

実装例

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, 1, -1};

struct location
{
	int x;
	int y;
	int colors;
	vector <int> trap;
};

struct probability
{
	int colors;
	double p;
};

struct compare
{
	bool operator()(const probability &a, const probability &b)
	{
        return a.p > b.p;
    }
};

class ColorfulMazeTwo {
private:
	vector<string> map;
	vector<probability> probabilityList;
	int width;
	int height;
	bool flag[50][50];

public:

	void initFlag()
	{
		for( int i = 0; i < 50; i++ )
			for( int j = 0; j < 50; j++ )
				flag[i][j] = false;
	}

	bool check( location &l, probability p )
	{
		if( l.x < 0 ) return false;
		if( l.y < 0 ) return false;
		if( l.x >= width ) return false;
		if( l.y >= height ) return false;

		if( map[l.y][l.x] == '#' ) return false;
		if( flag[l.y][l.x] == true ) return false;

		char floor = map[l.y][l.x];
		if( 'A' <= floor && floor <= 'G' )
		{
			int kind = floor - 'A';
			if(( p.colors & (1 << kind)) == 0 )
			{
				return false;
			}
			else
			{
				l.colors |= (1 << kind);
			}

		}

		return true;
	}

	void calcProbability( vector<int> &trap )
	{
		probabilityList.clear();
		probability prob;

		for( int i = 0; i < 128; i++ )
		{
			prob.colors = i;
			prob.p = 1.00;

			for( int kind = 0; kind < 7; kind++ )
			{
				if((i & (1 << kind)) != 0 )
				{
					prob.p *= ( 1.00 - trap[kind] / 100.0 );
				}
			}

			probabilityList.push_back(prob);
		}

		sort(probabilityList.begin(), probabilityList.end(), compare() );
	}

	double getProbability(vector<string> maze, vector<int> trap)
	{
		map = maze;
		stack<location> st;
		location l;
		width = maze[0].length();
		height = maze.size();
		int start_x = 0;
		int start_y = 0;

		calcProbability( trap );

		for( int i = 0; i < height; i++ )
		{
			int s = maze[i].find('$');
			if( s == maze[i].npos ) continue;

			start_x = s;
			start_y = i;
			break;
		}

		for( vector<probability>::iterator it = probabilityList.begin(); it != probabilityList.end(); it++ )
		{
			initFlag();

			l.x = start_x;
			l.y = start_y;
			l.trap = trap;
			l.colors = 0;
			st.push(l);

			while(!st.empty())
			{
				location newLocation;
				l = st.top();
				st.pop();

				// 現在地の確認
				char floor = map[l.y][l.x];
				// exit
				if( floor == '!' )
				{
					if( it->colors == l.colors )
					{
						return it->p;
					}
					else
					{
						continue;
					}
				}

				for( int i = 0; i < 4; i++ )
				{
					newLocation = l;
					newLocation.x += dx[i];
					newLocation.y += dy[i];
					if( check(newLocation, *it) )
					{
						flag[l.y][l.x] = true;
						st.push(newLocation);
					}
				}
			}
		}

		return 0.0;
	}
};

SystemTest

81個のテストケース全てに対して通過を確認。ああ大変だった。

TopCoder ColorfulBoxesAndBalls(SRM464 DIV2 Easy)

No comments 17 3月 2010 Under: DIV 2 Easy, TopCoder

問題概説

numRed個の赤い箱とボールがある。numBlue個の青い箱とボールがある(同じ色の箱とボールの数は同じ)。

赤い箱に赤いボールが入っていればonlyRed点が入る。青い箱に青いボールが入っていればonlyBlue点が入る。箱の色とボールの色が違えばbothColors点が入る。

問題

得点を最大化して下さい。

方針

箱の数とボールの数が一致しているから、赤いボールを青い箱に1つ入れたら、青いボールも赤い箱に1つ入れなければならない。

赤いボールを青い箱に入れるメリット(bothColors – onlyRed)が、青いボールを赤い箱に入れるデメリット(bothColors – onlyBlue)を上回っていれば入れる価値がある。逆も同様。

numRed = 2, numBlue = 3, onlyRed = 100, onlyBlue = 400, bothColors = 200

bothColors – onlyRed = 100, onlyRed – onlyBlue = -300。色の違う箱に入れることで100 – 300 = 200点損をするので同じ箱に入れる。

2 * 100 + 3 * 400 = 1400点が正解。

numRed = 2, numBlue = 3, onlyRed = 100, onlyBlue = 400, bothColors = 300

bothColors – onlyRed = 200, onlyRed – onlyBlue = -100。色の違う箱に入れることで200 – 100 = 100点得をするので可能な限り違う色に入れる。少ないのは赤だから、赤の数2個だけ入れ替える。

2 * 300 + 2 * 300 + 1 * 400 = 1600点が正解。

実装例

得をするか、損をするかを判定して、赤と青のどちらが多いかを調べるだけでよい。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

class ColorfulBoxesAndBalls {
public:
	int getMaximum(int numRed, int numBlue, int onlyRed, int onlyBlue, int bothColors)
	{
		int result;

		int subRed = bothColors - onlyRed;
		int subBlue = bothColors - onlyBlue;

		// 入れ替えた方が得をするケース
		if(subRed + subBlue > 0)
		{
			int min;
			bool blue;

			if( numRed < numBlue )
			{
				min = numRed;
				blue = false;
			}
			else
			{
				min = numBlue;
				blue = true;
			}

			// 少ない色の数だけ入れ替える
			if( blue == true )
			{
				result = min * 2 * bothColors + onlyRed * (numRed-numBlue);
			}
			else
			{
				result = min * 2 * bothColors + onlyBlue * (numBlue-numRed);
			}
		}

		// 入れ替えない方が得をするケース
		else
		{
			result = numRed * onlyRed + numBlue * onlyBlue;
		}

		return result;
	}
};

TopCoder ColorfulStrings(SRM464 DIV1 Easy, DIV2 Middle)

No comments 17 3月 2010 Under: DIV 1 Easy, DIV 2 Middle, TopCoder, 順列

問題概説

263を例に採る。263は6つの部分文字列( 2, 6, 3, 26, 63, 263 )に分解できる。各々の部分文字列のproduct valuesを計算する。

product valuesは各々の桁の積。2のproduct valueは2、263のproduct valueは2 * 6 * 3 = 36。部分文字列のproduct valuesは[ 2, 6, 3, 12, 18, 36 ]となる。全てのproduct valuesが異なる数値をColorfulStringと呼ぶ。

問題

n桁の数値の中で、k番目のColorfulStringを求めよ。なければ空文字列を返せ。

n = 3, k = 4

3桁(100〜999)の数値で4番目のColorfulStringを求める。順に”234″, “235″, “237″, “238″なので”238″を返せば正解。

n = 4, k = 2000

4桁(1000〜9999)の数値で2000番目のColorfulStringを求める。4桁のColorfulStringは2000個ないので”"を返せば正解。

n = 2, k = 22

2桁(10〜99)の数値で22番目のColorfulStringを求める。”52″を返せば正解。

n = 6, k = 464

6桁(100000〜999999)の数値で464番目のColorfulStringを求める。”257936″を返せば正解。

方針

素直にループを回してproduct valuesを計算してColorfulStringか判定してもExampleは全部通るが、SystemTestは通らない。そこで若干の工夫が必要。

同じ数字を含む数値はColorfulStringではない

例えば”22″の部分文字列は[ 2, 2, 22 ]となるが、2と2が同じ値なのでColorfulStringにはならない。これは桁数が大きくても同じである。

0や1を含む場合に注意

1 * x = xだから、2桁以上で1を含む場合はColorfulStringにはならない。例えば”21″の部分文字列は[ 2, 1, 21 ]で、product valuesは[ 2, 1, 2 ]なのでColorfulStringではない。

0 * x = 0であるから、同様に2桁以上ではColorfulStringにはならない。

1桁の場合は各桁ごとの掛け算が行われないため問題はない。

8桁より大きいColorfulStringはない

問題ではnの上限は50だが、実際は8より大きい場合は計算しなくてよい。

1桁以外で使える数値は2, 3, 4, 5, 6, 7, 8, 9の8種類に限られるので、9桁になると必ず重複が生じる。

順列を上手く使う

連番で数値を生成していって、上記の計算する必要のない場合を除外するよりも順列を上手く使うと手抜きができる。

C++を使うならnext_permutationが便利。next_permutationとprev_permutationは順列を生成する。例:

string s = "123";
do
{
	cout << s << endl;
}
while(next_permutation(s.begin(), s.end()));

実行結果

123
132
213
231
312
321

このように小さい順に同じ数字を使わない数を出力してくれるので、これを候補に絞り込みを行う。

同じ値は計算しない

順列を回して先頭のn桁だけ採っていると"234"と"235"の先頭の2桁は同じになる。これを毎回検討していると無駄なので計算しないことにする。

解答例

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <map>
#include <utility>
#include <set>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <sstream>
#include <complex>
#include <stack>
#include <queue>
using namespace std;
static const double EPS = 1e-5;
typedef long long ll;

class ColorfulStrings
{
public:

	vector<string> substrings( const string &s )
	{
		int length = s.length();
		string ss;
		vector<string> result;

		for( int i = 1; i <= length; i++ )
		{
			for( int j = 0; i + j <= length; j++ )
			{
				ss = s.substr( j, i );
				result.push_back(ss);
			}
		}

		return result;
	}

	int product_value( const string &s )
	{
		int value = 1;
		int length = s.length();

		for( int i = 0; i < length; i++ )
		{
			value *= (s[i] - '0');
		}

		return value;
	}

	string getKth_sub( int n, int k, string value )
	{
		set<int> dp;

		do
		{
			int v;

			string s = value.substr(0,n);
			v = atoi(s.c_str());

			if( dp.end() != dp.find(v) )
			{
				continue;
			}

			dp.insert(v);

			vector<string> sv = substrings(s);
			set<int> iset;
			bool flag = true;

			for( vector<string>::iterator it = sv.begin(); it != sv.end(); it++ )
			{
				int v = product_value( *it );
				// not found
				if( iset.end() == iset.find(v) )
					iset.insert(v);
				else
				{
					flag = false;
					break;
				}
			}

			if( flag == true )
			{
				k--;
			}

			if( k == 0 )
				return s;

		}
		while(next_permutation(value.begin(), value.end()));

		return "";
	}

	string getKth(int n, int k)
	{
		if( n == 1 )
		{
			return getKth_sub( n, k, "0123456789" );
		}

		if( n > 8 )
		{
			return "";
		}

		return getKth_sub( n, k, "23456789" );
	}
};

おわりに

上記のコードでSystemTestを通過することを確認した。

再帰で迷路を解く

No comments 30 12月 2009 Under: アルゴリズム, 再帰

再帰

再帰は自分で自分を呼び出すプログラミングの手法で、よく階乗の計算を例に用います。

int fact(int n) {
    if (n == 0) return 1; /* 脱出条件。0!は1である */
    return fact(n - 1) * n; /* n!は(n-1)!にnを乗じたもの。再帰呼び出し */
 }

このように再帰は

  1. 自分で自分にちょっと細工を加えて呼び出す
  2. 永久ループに陥らないために再起をしないで脱出する条件を決める

ことで定義します。

しかし、これでは面白みにかけます。別にこんなものはforでループを回してもいいし、むしろそうした方がよいのです。

再帰が有効なポイント

再帰は手順を示せるようなパズルに用いると面白いことができます。これは

  1. 現在の状況が解けるなら、現在の状況から1手動かした状態でも解けるはず
  2. 終了の条件がハッキリしている

ようなパズルなら再帰で解くことができるはずです。例えば迷路があり、その迷路から脱出するルーチンを考えます。

現在地( x, y )から脱出するルーチンmaze( int x, int y )があるのなら

int maze( int x, int y )
{
	maze( x + 1, y );
}

現在地から一歩動いた状態からでも脱出できるはずです。しかし、これでは永遠に右に進んでいくだけで再帰の脱出がありません。これをすこし掘り下げていきます。

まず迷路を造る

問題を解くのに迷路を与えなければいけないので、迷路を造ります。しかし、手で作っていくのも面倒だし、自動で生成することにします。ここで用いるのは棒倒し法というものです。

まず周囲を壁で囲う

柱を立てる

ランダムに柱を倒して出口を開ける

迷路らしくなったでしょうか。

コード

#include <iostream>
#include <stdlib.h>

#include <time.h>

#define SIZE 10

const char WALL = 9;
const char ROAD = 0;

//--------------------------------------------------
// 迷路を初期化する
//--------------------------------------------------
static void init( char maze[SIZE+1][SIZE+1] )
{
	// 更地にする
	for( int i = 0; i <= SIZE; i++ )
		for( int j = 0; j <= SIZE; j++ )
			maze[i][j] = ROAD;

	// 周りを壁で囲う
	for( int i = 0; i <= SIZE; i++ )
	{
		maze[i][0] = WALL;
		maze[i][SIZE] = WALL;
		maze[0][i] = WALL;
		maze[SIZE][i] = WALL;
	}

	// 出口を開ける
	maze[0][1] = ROAD;

	// 迷路を造る
	srand(time(NULL));
	for( int i = 2; i <= SIZE - 2; i += 2 )
		for( int j = 2; j <= SIZE - 2; j += 2 )
		{
			int r = rand() % 4;
			maze[i][j] = WALL; // 柱を立てる
			// 柱をランダムに4方向に倒す
			if( r == 0 )
				maze[i-1][j] = WALL; // 柱を倒す

			if( r == 1 )
				maze[i+1][j] = WALL; // 柱を倒す

			if( r == 2 )
				maze[i][j-1] = WALL; // 柱を倒す

			if( r == 3 )
				maze[i][j+1] = WALL; // 柱を倒す
		}

}

//--------------------------------------------------
// 迷路を表示する
//--------------------------------------------------

static void display( char maze[SIZE+1][SIZE+1] )
{
	for( int i = 0; i <= SIZE; i++ )
	{
		for( int j = 0; j <= SIZE; j++ )
		{
			if (maze[i][j] == WALL)
				std::cout << "■";

			if (maze[i][j] == ROAD)
				std::cout << " ";
		}

		std::cout << "\n";
	}
}

int main (int argc, char * const argv[])
{
	char maze[SIZE+1][SIZE+1];

	init(maze);
	display(maze);
    return 0;
}

こんな感じでOKです。SIZEを100にしてみると

かなり複雑な迷路まで自動生成してくれて驚きます。

迷路を解く

現在地( x, y )から脱出するルーチンmaze( int x, int y )は

int maze( int x, int y )
{
	maze( x + 1, y );
}

と書けそうですが、

  1. もし右に進めなかったらどうする?
  2. もし右に進んだときにゴールだったらどうする?

という問題に当たります。右に進めないときは右に行くという作戦は失敗だし、ゴールに到達するならそれ以上は進む必要がありません。したがって、これを再帰の脱出としてやればよいでしょう。これを4方向に対して行います。

static int maze( int x, int y, char map[SIZE+1][SIZE+1] )
{
	std::cout << x << "," << y << std::endl;

	if( map[x][y] == GOAL )
	{
		std::cout << "GOAL!!!" << std::endl;
		return SUCCESS;
	}

	map[x][y] = CURRENT;
	display(map);
	map[x][y] = ROAD;

	if( map[x+1][y] != WALL && map[x+1][y] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x + 1, y, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x-1][y] != WALL && map[x-1][y] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x - 1, y, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x][y+1] != WALL && map[x][y+1] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x, y + 1, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x][y-1] != WALL && map[x][y-1] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x, y - 1, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	return FAILURE;
}

だいぶ複雑になりましたが、ゴールの場合はSUCCESSを返して終了します。

次に壁である場合には進めないので次の条件を探すことにします。このままだと同じところをぐるぐる回ることもあり得るので、一度歩いたところはFOOTPRINTとして、ここも選ばないことにします。行き詰まって戻ってきたときはFOOTPRINTをROADに戻してやります。

それでも行き場がない場合はFAILUREを戻します。

完成図。点の部分を歩いて左上のゴールまで到達しています。このようにだいぶ複雑にはなったものの、単に自分で1コマ進めて自分を呼び出して、適切な再帰の脱出を設けてやることで迷路の脱出ができました。

30×30の迷路の例です。

長いので結果しか表示しませんでしたが、最終コードを実行すると探索の過程も表示されます。

最終ソースコード

#include <iostream>
#include <stdlib.h>
#include <time.h>
#define SIZE 10

const char WALL = 9;
const char ROAD = 0;
const char GOAL = 1;
const char CURRENT = 2;
const char FOOTPRINT = 3;

const int SUCCESS = 0;
const int FAILURE = 1;

//--------------------------------------------------
// 迷路を初期化する
//--------------------------------------------------
static void init( char maze[SIZE+1][SIZE+1] )
{
	// 更地にする
	for( int i = 0; i <= SIZE; i++ )
		for( int j = 0; j <= SIZE; j++ )
			maze[i][j] = ROAD;

	// 周りを壁で囲う
	for( int i = 0; i <= SIZE; i++ )
	{
		maze[i][0] = WALL;
		maze[i][SIZE] = WALL;
		maze[0][i] = WALL;
		maze[SIZE][i] = WALL;
	}

	// 出口を開ける
	maze[0][1] = GOAL;

	// 迷路を造る
	srand(time(NULL));
	for( int i = 2; i <= SIZE - 2; i += 2 )
		for( int j = 2; j <= SIZE - 2; j += 2 )
		{
			int r = rand() % 4;
			maze[i][j] = WALL; // 柱を立てる
			// 柱をランダムに4方向に倒す
			if( r == 0 )
				maze[i-1][j] = WALL; // 柱を倒す

			if( r == 1 )
				maze[i+1][j] = WALL; // 柱を倒す

			if( r == 2 )
				maze[i][j-1] = WALL; // 柱を倒す

			if( r == 3 )
				maze[i][j+1] = WALL; // 柱を倒す
		}

}

//--------------------------------------------------
// 迷路を表示する
//--------------------------------------------------

static void display( char maze[SIZE+1][SIZE+1] )
{
	for( int i = 0; i <= SIZE; i++ )
	{
		for( int j = 0; j <= SIZE; j++ )
		{
			if (maze[i][j] == CURRENT)
				std::cout << "☆";

			if (maze[i][j] == WALL)
				std::cout << "■";

			if (maze[i][j] == ROAD || maze[i][j] == GOAL )
				std::cout << " ";

			if (maze[i][j] == FOOTPRINT )
				std::cout << "・";

		}
		std::cout << "\n";
	}
	std::cout << "\n";
}

static int maze( int x, int y, char map[SIZE+1][SIZE+1] )
{
	std::cout << x << "," << y << std::endl;

	if( map[x][y] == GOAL )
	{
		std::cout << "GOAL!!!" << std::endl;
		return SUCCESS;
	}

	map[x][y] = CURRENT;
	display(map);
	map[x][y] = ROAD;

	if( map[x+1][y] != WALL && map[x+1][y] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x + 1, y, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x-1][y] != WALL && map[x-1][y] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x - 1, y, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x][y+1] != WALL && map[x][y+1] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x, y + 1, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	if( map[x][y-1] != WALL && map[x][y-1] != FOOTPRINT )
	{
		map[x][y] = FOOTPRINT;
		if( maze( x, y - 1, map ) == SUCCESS )
			return SUCCESS;
		map[x][y] = ROAD;
	}

	return FAILURE;
}

int main (int argc, char * const argv[])
{
	char map[SIZE+1][SIZE+1];

	init(map);
	display(map);

	maze( SIZE - 1, SIZE - 1, map );

	display(map);

    return 0;
}

Qualification Round 2009, B. Watersheds

No comments 29 12月 2009 Under: アルゴリズム, 再帰

Google Code Jam Qualification Round 2009, B. Watershedsの問題です。原文は最後に載せます。

問題

降水量の問題。四角く区切られた区分に雨が降り、一番低い隣接した部分に流れる。同じ高さがある場合は優先順位が北、西、東、南の順になる。現在の高さと隣の高さが同じときは流れない。これ以上流れる場所がない場所をsinkと言う。

雨が降って流れた先が同じsinkになるものは同じラベル(aとかbとか)をつける。

問題の入力の1行目は問題の数。例の場合は5なので5つの問題があるという意味。次の行の”3 3″は3 x 3のマップですよという意味。続いて3 x 3のデータがある。

その次が1 x 10のマップがありますという意味。以下同様に全部で5つの問題がある。

このマップにラベルをつけるとどうなるかが出力する。

再帰

再帰は自分で自分を呼び出すプログラミングの手法で、よく階乗の計算を例に用います。

int fact(int n) {
    if (n == 0) return 1; /* 脱出条件。0!は1である */
    return fact(n - 1) * n; /* n!は(n-1)!にnを乗じたもの。再帰呼び出し */
 }

原文

Problem

Geologists sometimes divide an area of land into different regions based on where rainfall flows down to. These regions are called drainage basins.

Given an elevation map (a 2-dimensional array of altitudes), label the map such that locations in the same drainage basin have the same label, subject to the following rules.

From each cell, water flows down to at most one of its 4 neighboring cells.
For each cell, if none of its 4 neighboring cells has a lower altitude than the current cell’s, then the water does not flow, and the current cell is called a sink.
Otherwise, water flows from the current cell to the neighbor with the lowest altitude.
In case of a tie, water will choose the first direction with the lowest altitude from this list: North, West, East, South.
Every cell that drains directly or indirectly to the same sink is part of the same drainage basin. Each basin is labeled by a unique lower-case letter, in such a way that, when the rows of the map are concatenated from top to bottom, the resulting string is lexicographically smallest. (In particular, the basin of the most North-Western cell is always labeled ‘a’.)

Input

The first line of the input file will contain the number of maps, T. T maps will follow, each starting with two integers on a line — H and W — the height and width of the map, in cells. The next H lines will each contain a row of the map, from north to south, each containing W integers, from west to east, specifying the altitudes of the cells.

Output

For each test case, output 1+H lines. The first line must be of the form

Case #X:

where X is the test case number, starting from 1. The next H lines must list the basin labels for each of the cells, in the same order as they appear in the input.
Limits

T ≤ 100;

Small dataset

1 ≤ H, W ≤ 10;
0 ≤ altitudes < 10.
There will be at most two basins.

Large dataset

1 ≤ H, W ≤ 100;
0 ≤ altitudes < 10,000.
There will be at most 26 basins.

Sample

Input Output
5
3 3
9 6 3
5 9 6
3 5 9
1 10
0 1 2 3 4 5 6 7 8 7
2 3
7 6 7
7 6 7
5 5
1 2 3 4 5
2 9 3 9 6
3 3 0 8 7
4 9 8 9 8
5 6 7 8 9
2 13
8 8 8 8 8 8 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8 8 8 8 8 8
Case #1:
a b b
a a b
a a a
Case #2:
a a a a a a a a a b
Case #3:
a a a
b b b
Case #4:
a a a a a
a a b b a
a b b b a
a b b b a
a a a a a
Case #5:
a b c d e f g h i j k l m
n o p q r s t u v w x y z

Notes

In Case #1, the upper-right and lower-left corners are sinks. Water from the diagonal flows towards the lower-left because of the lower altitude (5 versus 6).