*문제
세종대왕은 현수에게 현수가 다스릴 수 있는 영지를 하사하기로 했다. 전체 땅은 사각형으로 표 시된다. 그 사각형의 땅 중에서 세종대왕이 현수가 다스릴 수 있는 땅의 크기(세로의 길이와 가 로의 길이)를 정해주면 전체 땅 중에서 그 크기의 땅의 위치를 현수가 정하면 되는 것이다. 전체 땅은 사각형의 모양의 격자로 되어 있으며, 그 사각형 땅 안에는 많은 오렌지 나무가 심 겨져 있다. 현수는 오렌지를 무척 좋아하여 오렌지 나무가 가장 많이 포함되는 지역을 선택하 고 싶어 한다. 현수가 얻을 수 있는 영지의 오렌지 나무 최대 개수를 출력하는 프로그램을 작 성하세요. 다음과 같은 땅의 정보가 주어지고, 현수가 하사받을 크기가, 가로 2, 세로 3의 크 기이면 가장 많은 오렌지 나무가 있는 영지는 총 오렌지 나무의 개수가 16인 3행 4열부터 시 작하는 구역이다.
3 5 1 3 1 3 2
1 2 1 3 1 1 2
1 3 1 5 1 3 4
5 1 1 3 1 3 2
3 1 1 3 1 1 2
1 3 1 3 1 2 2
*문제 읽기
1) 가로 * 세로 길이의 territory 크기를 입력받는다. (5<=H, W<=50)
2) territory 의 각 영역안에 오렌지나무의 개수를 입력받는다. (1~9)
3) 현수가 받을 크기의 땅 입력받기
4) 현수가 얻을 수 있는 오렌지 나무의 최대 개수 출력
*생각
1) 배열에 입력 다 받는 건 OK
2)해당영역이 최대 개수의 오렌지 나무를 가지는지 알아봐야 함
3)이중 가장 큰값을 구함 temp써서 최대값 구하고 max인지 확인한 후 max가 맞으면 집어 넣기
SMALL 풀이 방법 - 브루트포스
*코드 구현
// 50번 - territory 선택 (small)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int a[51][51];
int main() {
//freopen("input.txt", "rt", stdin);
int h, w, n, m, sum, max=-999999999;
scanf("%d %d", &h, &w);
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
scanf("%d", &a[i][j]);
}
}
scanf("%d %d", &n, &m);
for (int i = 0; i < h-n+1; i++) {
for (int j = 0; j < w-m+1; j++) {
sum = 0;
for (int k = i; k < i + n; k++) {
for (int s = j; s < j + m; s++) {
sum = sum + a[k][s];
}
}
if (sum > max) max = sum;
}
}
printf("%d\n", max);
}
- 영지 territory의 크기 h와 w를 입력받고 그만큼 수를 채워 넣음
- 4중 for문이 돌면서 일일이 다 세는 것
*풀이
1) 4중 for문을 써야함.
2)
2*3 다 더하면 됨
3)
노란지점까지 다 더하면됨.
그 이후는 영역을 벗어나버리니깐
* 수가 적다면 4중 for문으로 주어진 행렬에서 일부 행렬의 합을 구할 수 있다.
* i / j 에서 영역이 튀어나가지 않게 더해주는 것이 중요. 일부행렬의 크기를 고려하여 어디까지 for문을 돌지 잘 염두해둬야 함
LARGE 풀이 방법 - 다이나믹 프로그래밍(DP)
=> 앞서 사용했던 4중 for문을 2중 for문으로 줄일 수 있다.
*풀이:
1) 다이나믹 프로그래밍 테이블을 만든다.
- 읽으면서 누적한다.
2) 다음 행은 다른 방법으로 누적이다.
3) 다이나믹 값을 구하는 식
- 점 찍은 곳은 핑크 사각형의 총 합임
- 다이나믹 프로그래밍 테이블의 18 값은 왼쪽의 원래 배열에서의 핑크색 박스의 합
- 점찍은 곳 구하는 방법 : 18+19
- 우리가 구하고자 하는 값: 초록
- 검은색 부분이 교집합은 빼줘야 한다.
왜냐하면 18과 19 자리를 구할 때 2번 더해진 부분이기 때문이다. 그렇기 때문에 그만큼 교집합 부분을 1번 빼줘야한다.
- 최종 연산: 18 + 19 - 13 + 5
13: 교집합 부분(중복된 부분)
5: 새로 합해야하는 부분(현재 scanf로 읽혀지는 a행렬의 값)
- 식으로 표현하면
dy[i][j] = dy[i][j-1] + dy[i-1][j] - dy[i-1][j-1] + a[i][j]
- 첫줄이나 그런 부분들은 누적 0으로 계산한다.
- 검은 영역 부분을 구하려면 다이나믹프로그래밍 테이블의 53번째를 이용해야함(누적된 것이니깐)
- 근데 1.1부터 쭉~~누적해온것이기 때문에 영지의 크기만큼 그 전까지의 사각형을 빼줘야한다.
- 영지의 크기 2*3이기 때문에 53위치에서 각각 i-2 / j-3 해준 부분을 빼준다.
- 교집합을 2번빼게 되기 때문에 다시 한번 더 해줘야한다.
dy[i][j] - dy[i-2][j] - dy[i][j-3] + dy[i-2][j-3]
- 주의 할 점은 dy테이블은 이미 누적이기 때문에 1.1부터 구하는게 아니라 입력된 territory의 크기 만큼 누적된 그 끝부터 돌게 됨
n과 m부터 돌아야한다.
- dy테이블에서 최대 오렌지나무 영역 구하는 부분 재설명
*코드구현
// 51번 - territory 선택 (large) 다이나믹 프로그래밍
// small 4중 for문에서 => 2중 for문으로
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int a[701][701];
int dy[701][701]; //추가
int main() {
freopen("input.txt", "rt", stdin);
int h, w, n, m, sum, max = -999999999;
scanf("%d %d", &h, &w);
//1부터 시작해야 이전 값 0을 더할 수 있음
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
scanf("%d", &a[i][j]);
//추가: 읽으면서 dy테이블 만들기
//주의: 현재값 a[i][j]를 더해주는 것 잊지말기
dy[i][j] = dy[i - 1][j] + dy[i][j - 1] - dy[i - 1][j - 1] + a[i][j];
}
}
scanf("%d %d", &n, &m);
//시작을 2행 3열부터 해야함
for (int i = n; i <=h; i++) {
for (int j = m; j <=w; j++) {
//주어진영역의 합
sum = dy[i][j] - dy[i - n][j] - dy[i][j - m] + dy[i - n][j - m];
if (sum > max) max = sum;
}
}
printf("%d\n", max);
}
기억할 것
* 입력값을 읽으면서 다이나믹 프로그래밍 테이블을 만들 수 있다. (누적값 계산) - 중복값 빼고 현재값 더해주는 것 잊지않기!
* 다이나믹 프로그래밍 테이블을 따로 만들어 줘야한다.
* 누적 값이 계산되는 것이기 때문에 교집합으로 중복덧셈이나 중복 뺄셈이 있는지 확인해줘야 한다.
* 다이나믹 프로그래밍 테이블의 일부를 덧셈할 때는 for문의 시작점을 잘 봐야한다.
'[알고리즘] 문제풀이 연습' 카테고리의 다른 글
[알골90제] 53번 - K진수 출력 : 스택 자료구조 (0) | 2019.07.30 |
---|---|
[알골90제] 52번 - Ugly Numbers : 투포인트 알고리즘 응용 (0) | 2019.07.30 |
[알골90제] 49번 - 쌓기 블록의 최대값 (2차원 배열 응용) (0) | 2019.07.28 |
[알골90제] 48번 - 각 행의 평균과 가장 가까운 값 (2차원 배열 탐색) (0) | 2019.07.28 |
[알골90제] 47번 - 봉우리 (2차원 배열 탐색) (0) | 2019.07.26 |