読者です 読者をやめる 読者になる 読者になる

Lilliput Steps

小さな一歩から着実に. 数学やプログラミングのことを書きます.

JOI2007 本選

JOI 本選練習

友達に勉強を教えながら, ゆったり2007を解いてみました.
4 -> 3 -> 2 -> 1 -> 5 の順で解きました. 4 が一番簡単なんだよなあ...
1時間半くらい余らせて終了.

1. 碁石並べ

シミュレーションを行う. オセロっぽい操作を行う.

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> P;

int main()
{
	int n;
	while (scanf("%d", &n) && n){
		vector<P> line;
		for (int i = 0; i < n; i++){
			int col;
			scanf("%d", &col);
			if (!i) line.push_back(make_pair(col, 1));
			else if (i % 2 == 0){
				if (line.size() && line[line.size() - 1].first == col){
					line[line.size() - 1].second++;
				}
				else line.push_back(make_pair(col, 1));
			}
			else {
				if (line[line.size() - 1].first == col) line[line.size() - 1].second++;
				else {
					line[line.size() - 1].first = col;
					line[line.size() - 1].second++;
					if (line.size() > 1 && line[line.size() - 2].first == col){
						line[line.size() - 2].second += line[line.size() - 1].second;
						line.erase(line.end() - 1, line.end());
					}
				}
			}
		}
		int sum = 0;
		for (int i = 0; i < line.size(); i++) if (line[i].first == 0) sum += line[i].second;
		
		printf("%d\n", sum);
	}
	return (0);
}

2. 共通部分文字列

共通部分列と勘違いしてヤバい式を入れて1 WA をいただきました. 気をつけます...

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long lint;

int main()
{
	char s[4096], t[4096];
	static int dp[2][4096];
	
	while (scanf("%s", s) == 1){
		scanf("%s", t);
		
		memset(dp, 0, sizeof(dp));
		
		int ans = 0;
		
		for (int i = 1; s[i - 1]; i++){
			for (int j = 1; t[j - 1]; j++){
				if (s[i - 1] == t[j - 1])
					dp[i & 1][j] = dp[(i - 1) & 1][j - 1] + 1;
				else dp[i & 1][j] = 0;
				ans = max(ans, dp[i & 1][j]);
			}
		}
		
		printf("%d\n", ans);
	}
	
	return (0);
}

3. ダーツ

今となっては有名な半分全列挙の問題. upper_bound便利.

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long lint;

int main()
{
	lint n, m;
	lint score[1024];
	static lint pSum[512 * 1023];
	
	while (scanf("%lld %lld", &n, &m) && n){
		
		for (int i = 0; i < n; i++){
			scanf("%lld", score + i);
		}
		score[n++] = 0;
		
		int k = 0;
		for (int i = 0; i < n; i++){
			for (int j = i; j < n; j++){
				pSum[k++] = score[i] + score[j];
			}
		}
		
		sort(pSum, pSum + k);
		
		lint res = 0;
		for (int i = 0; i < k; i++){
			if (pSum[i] > m) break;
			res = max(res, pSum[i] + *(upper_bound(pSum, pSum + k, m - pSum[i]) - 1));
		}
		
		printf("%lld\n", res);
	}
	return (0);
}

4. ぴょんぴょん川渡り

メモ化再帰氏を使う. DPでやったときバグらせたのを思い出した. (DP苦手らしい)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>

using namespace std;

int k[256];
int x[256][16], d[256][16];
int memo[256][16][128];
int n, m;

int getMin(int nowY, int idx, int left)
{
	if (nowY == n) return (0);
	if (~nowY && memo[nowY][idx][left] >= 0) return (memo[nowY][idx][left]);
	
	int res = INT_MAX;
	
	for (int i = 0; i < k[nowY + 1]; i++){
		int bad = (nowY == -1 || nowY == n - 1 ? 0 : (d[nowY][idx] + d[nowY + 1][i]) * abs(x[nowY][idx] - x[nowY + 1][i]));
		res = min(res, getMin(nowY + 1, i, left) + bad);
	}
	
	if (left && nowY + 2 <= n){
		for (int i = 0; i < k[nowY + 2]; i++){
			int bad = (nowY == -1 || nowY == n - 2 ? 0 : (d[nowY][idx] + d[nowY + 2][i]) * abs(x[nowY][idx] - x[nowY + 2][i]));
			res = min(res, getMin(nowY + 2, i, left - 1) + bad);
		}
	}
	
	if (~nowY) memo[nowY][idx][left] = res;
	return (res);
}

int main()
{
	while (scanf("%d %d", &n, &m) && n){
		
		for (int i = 0; i < n; i++){
			scanf("%d", k + i);
			for (int j = 0; j < k[i]; j++){
				scanf("%d %d", &x[i][j], &d[i][j]);
			}
		}
		k[n] = 1;
		
		memset(memo, -1, sizeof(memo));
		
		printf("%d\n", getMin(-1, 0, m));
		
	}
	return (0);
}

5. ペンキの色

座標圧縮氏を行う. 境界周りの処理が少しむずかしいが, 点Xを区間[X, X + 1]に対応させると解きやすい.
今年の予選 5 の応用っぽい問題. JOI は過去問力がある程度試される気がする.

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>

using namespace std;

typedef pair<int, int> P;

bool exist[2048][2048];
int x1[1024], y1[1024], x2[1024], y2[1024];
vector<int> xs, ys;
map<int, int> convX, convY;

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


int main()
{
	int X, Y, N;
	
	while (scanf("%d %d", &X, &Y) && X){
		scanf("%d", &N);
		
		for (int i = 0; i < N; i++){
			scanf("%d %d %d %d", x1 + i, y1 + i, x2 + i, y2 + i);
			xs.push_back(x1[i]); xs.push_back(x2[i]);
			ys.push_back(y1[i]); ys.push_back(y2[i]);
		}
		
		xs.push_back(0); xs.push_back(X - 1);
		ys.push_back(0); ys.push_back(Y - 1);
		
		sort(xs.begin(), xs.end());
		sort(ys.begin(), ys.end());
		
		xs.erase(unique(xs.begin(), xs.end()), xs.end());
		ys.erase(unique(ys.begin(), ys.end()), ys.end());
		
		for (int i = 0; i < xs.size(); i++) convX[xs[i]] = i;
		for (int i = 0; i < ys.size(); i++) convY[ys[i]] = i;
		
		memset(exist, 0, sizeof(exist));
		
		for (int i = 0; i < N; i++){
			for (int y = convY[y1[i]]; y < convY[y2[i]]; y++){
				for (int x = convX[x1[i]]; x < convX[x2[i]]; x++){
					exist[y][x] = true;
				}
			}
		}
		
		int ret = 0;
		
		for (int y = 0; y <= convY[Y - 1]; y++){
			for (int x = 0; x <= convX[X - 1]; x++){
				if (!exist[y][x]){
					exist[y][x] = true;
					queue<P> q;
					
					for (q.push(P(y, x)); q.size(); q.pop()){
						P x = q.front();
						for (int i = 0; i < 4; i++){
							int ny = x.first + dy[i], nx = x.second + dx[i];
							if (0 <= ny && ny <= convY[Y - 1] &&
									0 <= nx && nx <= convX[X - 1] &&
										exist[ny][nx] == false){
								exist[ny][nx] = true;
								q.push(P(ny, nx));
							}
						}
					}
					ret++;
				}
			}
		}
		printf("%d\n", ret);
	}
	return (0);
}