Volume 5 / 0540->0541

情報オリンピック 2009本選 解答
公式のページはJOI 2008-2009 本選 問題・データ
前回Q1~Q3を解いたときの記事は『Volume 5 / 0538->0540(2009年11月28日)

Problem 0540 : Amidakuji / 4: あみだくじ

むずかしかった。解説見ても解くのに手間取った。
解き方は解説の通り。
のソートはなかなか強力っぽいから、使い方完璧にしとかないと。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


// 横棒の構造体
typedef struct CROSS_TAG {
	int right_num, left_num;
	int right_point, left_point;
	int r, l, h;
} CROSS;

vector<CROSS> crosses;
int points[1001];
int n; // 縦棒の本数
int m; // 横棒の本数
int h; // 棒の長さ
int k; // 選ぶ棒の本数

// ソート用
bool cross_cmp( const CROSS &left, const CROSS &right )
{
	return left.h < right.h;
}

int calc()
{
	int i, j;
	int work[1001], tmp;


	//// 線の番号
	// 初期化
	for( i=1; i<=n; i++ )
		work[i] = i;
	// 上の横棒から順に走査して入れ替え
	for( i=0; i<m; i++ ){
		crosses[i].left_num = work[crosses[i].l];
		crosses[i].right_num = work[crosses[i].r];

		tmp = work[crosses[i].l];
		work[crosses[i].l] = work[crosses[i].r];
		work[crosses[i].r] = tmp;
	}

	//// 線のスコア
	// 初期化
	for( i=1; i<=n; i++ ){
		work[i] = points[i-1];
	}
	// 下の横棒から順に走査して入れ替え
	for( i=m-1; i>=0; i-- ){
		crosses[i].left_point = work[crosses[i].l];
		crosses[i].right_point = work[crosses[i].r];

		tmp = work[crosses[i].l];
		work[crosses[i].l] = work[crosses[i].r];
		work[crosses[i].r] = tmp;
	}

	// 横棒を消去しないときのスコア
	int score0 = 0;
	for( i=1; i<=k; i++ ){
		score0 += work[i];
	}

	// 横棒を消去したときの減少値の最大を求める
	int max = 0;
	for( i=0; i<m; i++ ){
		// 両方ともk番目(以内|の範囲外)の場合は処理をする必要がない
		if( ( crosses[i].left_num <= k && crosses[i].right_num <= k )
		 || ( crosses[i].left_num > k && crosses[i].right_num > k ) )
			continue;

		// 左側がk番目以内
		if( crosses[i].left_num <= k ){
			// 曲がったほうがいいとき
			if( crosses[i].left_point >= crosses[i].right_point )
				continue;
			// 棒を消去したときの減少値
			int d = crosses[i].right_point - crosses[i].left_point;
			if( max < d )
				max = d;
		}

		// 右側がk番目以内
		if( crosses[i].right_num <= k ){
			// 曲がったほうがいいとき
			if( crosses[i].left_point <= crosses[i].right_point )
				continue;
			// 棒を消去したときの減少値
			int d = crosses[i].left_point - crosses[i].right_point;
			if( max < d )
				max = d;
		}
	}

	// 横棒を消去したスコアを返す
	return score0 - max;
}

int main(void)
{
	int i;

	while( cin >> n >> m >> h >> k, n != 0 && m != 0 && h != 0 && k != 0 ){

		crosses.resize(m);

		// 入力
		for( i=0; i<n; i++ ){
			cin >> points[i];
		}
		for( i=0; i<m; i++ ){
			cin >> crosses[i].l >> crosses[i].h;
			crosses[i].r = crosses[i].l+1;
		}

		// 横棒を高さでソート
		sort( crosses.begin(), crosses.end(), cross_cmp );

		// 結果の出力
		cout << calc() << endl;

	}

	return 0;
}

Problem 0541 : Walk / 4: 散歩

DPっぽい気はしたけど、自力では形にできなかった。
解説さまさま。

#include <iostream>

using namespace std;

#define MAX_WH 1000


int map[MAX_WH+2][MAX_WH+2];
int cnt[MAX_WH+2][MAX_WH+2][2];
int w, h;
int n;

// n回目の散歩のときの道の状態を生成
void reflesh()
{
	int sum;
	int x, y;

	if( n == 1 ) return;

	cnt[1][1][(map[1][1]+1)%2] = (n-1)/2;
	cnt[1][1][map[1][1]] = (n-1) - cnt[1][1][(map[1][1]+1)%2];
	map[1][1] = (map[1][1]+(n-1)%2)%2;

	for( y=1; y<=h; y++ ){
		for( x=1; x<=w; x++ ){
			if( y == 1 && x == 1 ) continue;
			sum = cnt[y-1][x][0] + cnt[y][x-1][1];
			cnt[y][x][(map[y][x]+1)%2] = sum/2;
			cnt[y][x][map[y][x]] = sum - cnt[y][x][(map[y][x]+1)%2];
			map[y][x] = (map[y][x]+sum%2)%2;
		}
	}

	return;
}

// n回目の散歩のシミュレーション・結果の出力
void check()
{
	int x, y;

	x = 1; y = 1;
	while( x != w+1 && y != h+1 ){
		if( map[y][x] )
			x++;
		else
			y++;
	}

	// 結果の出力
	cout << y << " " << x << endl;

	return;
}

int main(void)
{
	int x, y;
	int i, j;

	while( cin >> h >> w >> n, h != 0 && w != 0 && n != 0 ){
		// 入力と初期化
		for( y=0; y<=h+1; y++ ){
			for( x=0; x<=w+1; x++ ){
				cnt[y][x][0] = cnt[y][x][1] = 0;
				if( y != 0 && y != h+1 && x != 0 && x != w+1 ){
					cin >> map[y][x];
				}
			}
		}

		reflesh();

		check();

	}

	return 0;
}

Problem 0542 : Authentication Level / 5: 認証レベル

解法はわかったんだけど、priority_queueを使わなかったらTLE。
priority_queueはじめて使った。なかなか早い。

#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;

#define W_MAX 500
#define H_MAX 500
#define R_MAX 250000
#define L_MAX 100000000


int map[2][H_MAX+2][W_MAX+2];	// 部屋の配置
bool v[2][H_MAX+2][W_MAX+2];	// 訪れた部屋の数(探索用)
int rl[2][R_MAX+1];				// r部屋訪れるために必要な認証レベル
int w[2], h[2];					// 部屋の数(横、縦)
int ex[2], ey[2];				// エレベーターの座標
priority_queue< pair< int, pair< int, int > > > rooms; // 訪問する予定の部屋(レベル,x座標,y座標)

// r部屋訪れるために必要な認証のレベルを調べる(配列rl[][]の更新)
void check( int k )
{
	int i, j;
	int dx[4] = { 0, 1, 0, -1 };
	int dy[4] = { 1, 0, -1, 0 };
	int count_room = 0;			// 訪れた部屋の数をカウント
	int current_level = 0;		// 現在の認証レベルを保持
	int x, y, level;

	// キューを空にしておく
	while( !rooms.empty() ) rooms.pop();

	// 初期化(四辺は"訪問済み"で囲っておく)
	for( i=0; i<=h[k]+1; i++ )
		for( j=0; j<=w[k]+1; j++ )
			v[k][i][j] = ( i==0 || i==h[k]+1 || j==0 || j==w[k]+1 ) ? true : false;

	// エレベーターの位置からスタート
	rooms.push( make_pair( -1, make_pair( ex[k], ey[k] ) ) );

	while( rooms.size() > 0 && count_room <= w[k]*h[k] ){
		// levelが一番低いものを取り出す
		x = rooms.top().second.first;
		y = rooms.top().second.second;
		level = -rooms.top().first;
		rooms.pop();

		// その座標が既に訪問済み
		if( v[k][y][x] ) continue;

		// 訪問済みにする
		v[k][y][x] = true;

		// 最小の認証レベルが現在の認証レベルより大きいときは更新
		if( current_level < level )
			current_level = level;

		// 訪問済みの部屋数を増やし、その数に達するのに必要だった認証レベルを配列に格納
		count_room++;
		rl[k][count_room] = current_level;

		// 未訪問の部屋をキューに追加
		for( i=0; i<4; i++ ){
			if( !v[k][y+dy[i]][x+dx[i]] ){
				rooms.push( make_pair( -map[k][y+dy[i]][x+dx[i]], make_pair( x+dx[i], y+dy[i] ) ) );
			}
		}
	}

	return;
}

int main(void)
{
	int k, i, x, y;
	int r;

	while( cin >> r, r!=0 ){
		for( k=0; k<2; k++ ){
			// 入力
			cin >> w[k] >> h[k] >> ex[k] >> ey[k];
			for( y=1; y<=h[k]; y++ ){
				for( x=1; x<=w[k]; x++ ){
					cin >> map[k][y][x];
				}
			}
			// 初期化
			for( i=0; i<=w[k]*h[k]; i++ )
				rl[k][i] = 0;

			// rl[][]の更新
			check( k );
		}

		int min = L_MAX;
		int new_val;
		// 認証レベルの和が最小となるものを探す
		for( i=0; i<=r; i++ ){
			if( i > w[0]*h[0] || r-i > w[1]*h[1] )
				continue;

			new_val = 0;

			new_val += rl[0][i];
			new_val += rl[1][r-i];

			if( min > new_val )
				min = new_val;
		}

		// 結果の出力
		cout << min << endl;
	}

	return 0;
}