백준 16235 - 나무 재테크

4 minute read

문제링크

문제설명

  • 시간제한(0.3초)
    • 1초에 가능한 연산양은 O(n)을 기준으로 대략 n이 1억개일 때 가능한 시간이라고 본다.
    • 0.3초는 대략 n이 3천만 정도임을 생각하고 가자
  • NxN 크기의 땅에 양분이 있고, 각 칸에는 두개 이상의 나무를 심을 수 있다.
  • 나무는 사계절 동안 아래와 같은 시간을 보낸다.
    • 나무가 자신이 위치한 칸에서 나이만큼 양분을 먹고, 나이가 1 증가한다.
    • 같은 칸에 위치한 나무가 여러 개인 경우 나이가 적은 나무부터 양분을 먹는다.
    • 만약 양분이 부족해서 자신의 나이만큼 양분을 먹을 수 없는 나무는 즉시 죽는다.
    • 여름
    • 봄에 죽은 나무가 양분으로 변한다. 각각의 나무의 나이를 2로 나눈 값(소수점 무시)을 각 칸에 더해준다.
    • 가을
    • 나이가 5의 배수인 나무는 번식한다.
    • 번식은 인접한 8개의 칸으로 가능하며, 땅을 벗어나지 않는 칸에 나이가 1인 나무가 생겨난다.
    • 겨울
    • S2D2 라는 로봇이 입력으로 받은 추가하는 양분 A[r][c] 를 각 칸에 추가해준다.
  • K년이 지난 후 살아남은 나무의 수를 출력한다.

문제접근(시간초과)

  • 나무의 정보를 표현하는 Tree 구조체 (xpos, ypos, age)를 선언한다.
  • 봄과 여름은 하나의 함수로 구현한다. 이때, 양분을 나이 순서대로 먹기 위해서 sort 함수를 통해 나이를 기준으로 오름차순 정렬을 해준다.
    • 구조체의 경우 여러 개의 변수를 정렬하는 것과 같으므로, nlog(n)의 시간복잡도를 가지는 sort 함수에 실질적으로는 3nlog(3n)이 들어간 연산량과 같다.
  • 위의 이유로 시간초과가 발생했다고 생각했고, 구조체 정렬이 아닌 방법을 생각해보아야 했다.

구현(42% 시간초과)

#include <iostream>
#include <vector>
#include <memory.h>
#include <algorithm>
using namespace std;

typedef struct _tree {
	int xpos, ypos, age;
} Tree;

int N, M, K;
int A[101][101] = { 0, };
int land[101][101];
vector<Tree> treeList;
int ans = 0; // 살아 있는 나무의 개수
int dx[8] = { -1, 1, 0, 0, -1, -1, 1, 1 };
int dy[8] = { 0, 0, -1, 1, 1, -1, 1, -1 };

void printLand() {
	printf("###################################\n");
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			printf("%d ", land[r][c]);
		}
		printf("\n");
	}
}

// 나이가 어린 순서부터 오름차순 정렬
bool cmpAge(Tree t1, Tree t2) {
	return t1.age < t2.age;
}

void springSummer() {
	int nland[101][101]; // 죽은 나무들의 양분이 더해진 땅의 상태
	vector<Tree> ntreeList;
	memcpy(nland, land, sizeof(land));
	sort(treeList.begin(), treeList.end(), cmpAge);
	for (int i = 0; i < treeList.size(); i++) {
		int x = treeList[i].xpos;
		int y = treeList[i].ypos;
		int age = treeList[i].age;
		if (land[x][y] >= age) {
			land[x][y] -= age;
			nland[x][y] -= age;
			treeList[i].age++;
			ntreeList.push_back(treeList[i]);
		}
		else {
			ans--;
			nland[x][y] += (age / 2); // 죽은 나무는 여름에 양분으로 변함
		}
	}
	memcpy(land, nland, sizeof(nland));
	treeList = ntreeList;
}

void fall() {
	int len = treeList.size();
	for (int i = 0; i < len; i++) {
		if (treeList[i].age % 5 != 0) continue;
		for (int dir = 0; dir < 8; dir++) {
			int nextX = treeList[i].xpos + dx[dir];
			int nextY = treeList[i].ypos + dy[dir];
			if (nextX<1 || nextX>N || nextY<1 || nextY>N) continue;
			Tree ntree;
			ntree.xpos = nextX; ntree.ypos = nextY;
			ntree.age = 1;
			treeList.push_back(ntree);
			ans++;
		}
	}
}

void winter() {
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			land[r][c] += A[r][c];
		}
	}
}

int main()
{
	cin.tie(NULL);
	ios_base::sync_with_stdio(false);

	scanf("%d %d %d", &N, &M, &K);
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			scanf("%d", &A[r][c]);
			land[r][c] = 5;
		}
	}

	Tree tree;
	for (int i = 0; i < M; i++) {
		scanf("%d %d %d", &tree.xpos, &tree.ypos, &tree.age);
		treeList.push_back(tree);
		ans++;
	}
	for (int i = 0; i < K; i++) {
		springSummer();
		fall();
		winter();
	}
	printf("%d\n", ans);
	return 0;
}

문제접근(Solved)

  • vector treeTable[x][y] 형태의 인접그래프를 생성하여 (x,y)에 위치한 나무의 나이를 담도록 한다.
  • 위 처럼 나무 데이터를 관리할 경우 int 변수 한가지에 대해서만 정렬이 가능하므로 시간초과가 났던 방법에 비해 연산량을 1/3 정도로 줄일 수 있다.

구현

#include <iostream>
#include <vector>
#include <memory.h>
#include <algorithm>
using namespace std;

int N, M, K;
int A[11][11] = { 0, };
int land[11][11];
int lenTable[11][11];
int ans = 0; // 살아 있는 나무의 개수
int dx[8] = { -1, 1, 0, 0, -1, -1, 1, 1 };
int dy[8] = { 0, 0, -1, 1, 1, -1, 1, -1 };
vector<int> treeTable[101][101]; // treeTable[x][y][i] 는 x,y에 위치한 i번째 tree

void springSummer() {
	int nland[11][11]; // 죽은 나무들의 양분이 더해진 땅의 상태
	memcpy(nland, land, sizeof(land));
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			// 나이 순서대로 오름차순 정렬
			vector<int> tmp;
			sort(treeTable[r][c].begin(), treeTable[r][c].end());
			int len = lenTable[r][c];
			for (int i = 0; i < len; i++) {
				if (land[r][c] >= treeTable[r][c][i]) {
					land[r][c] -= treeTable[r][c][i];
					nland[r][c] -= treeTable[r][c][i];
					tmp.push_back(treeTable[r][c][i]+1);
				}
				else {
					ans--;
					nland[r][c] += (treeTable[r][c][i] / 2); // 죽은 나무의 양분
				}
			}
			treeTable[r][c].clear();
			for (auto ele : tmp) {
				treeTable[r][c].push_back(ele);
			}
			lenTable[r][c] = treeTable[r][c].size();
		}
	}
	memcpy(land, nland, sizeof(nland));
}

void fall() {
	int nlenTable[11][11];
	memcpy(nlenTable, lenTable, sizeof(lenTable));
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			int len = lenTable[r][c];
			for (int i = 0; i < len; i++) {
				if (treeTable[r][c][i] % 5 != 0) continue;
				for (int dir = 0; dir < 8; dir++) {
					int nextX = r + dx[dir];
					int nextY = c + dy[dir];
					if (nextX<1 || nextX>N || nextY<1 || nextY>N) continue;
					treeTable[nextX][nextY].push_back(1);
					nlenTable[nextX][nextY]++;
					ans++;
				}
			}
		}
	}
	memcpy(lenTable, nlenTable, sizeof(nlenTable));
}

void winter() {
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			land[r][c] += A[r][c];
		}
	}
}

int main()
{
	cin.tie(NULL);
	ios_base::sync_with_stdio(false);

	scanf("%d %d %d", &N, &M, &K);
	for (int r = 1; r <= N; r++) {
		for (int c = 1; c <= N; c++) {
			scanf("%d", &A[r][c]);
			land[r][c] = 5;
		}
	}
	
	memset(lenTable, 0, sizeof(lenTable));
	int x, y, age;
	for (int i = 0; i < M; i++) {
		scanf("%d %d %d", &x, &y, &age);
		treeTable[x][y].push_back(age);
		lenTable[x][y]++;
		ans++;
	}
	
	for (int i = 0; i < K; i++) {
		springSummer();
		fall();
		winter();
	}
	printf("%d\n", ans);
	return 0;
}

Leave a comment