문제 링크
https://www.acmicpc.net/problem/17779
17779번: 게리맨더링 2
재현시의 시장 구재현은 지난 몇 년간 게리맨더링을 통해서 자신의 당에게 유리하게 선거구를 획정했다. 견제할 권력이 없어진 구재현은 권력을 매우 부당하게 행사했고, 심지어는 시의 이름
www.acmicpc.net
접근 방법
- 맵을 입력 받을 때 전체 인구수를 구한다
- 경계선을 구한다
- fifth배열에 경계선을 true로 표시한다.
- 1~4 선거구 범위에 맞춰 popu 배열에 각 선거구의 인구수를 저장한다
- 5번 선거구 = 전체 인구수 - popu배열 전체 합
- popu 배열을 정렬해서 최대값과 최소값의 차이를 구해 전체 인구 차이의 최소값을 갱신한다.
1. 맵을 입력 받을 때 전체 인구수를 구한다
totalPopu = 0; //전체 인구수를 저장하기 위한 변수
map = new int[N + 1][N + 1];
for (int i = 1; i <= N; i++) {
StringTokenizer tokens = new StringTokenizer(br.readLine());
for (int j = 1; j <= N; j++) {
map[i][j] = Integer.parseInt(tokens.nextToken());
totalPopu += map[i][j]; //총 인구수를 구함
}
}
경계선을 정하고 선거구 범위에 맞춰 인구수를 구한다면, 5번 선거구의 인구는 전체인구 - 1~4번 선거구 인구로 계산할 수 있다.
2. 경계선을 구한다.
- 기준점 (x, y)
- 경계의 길이 d1, d2
배열의 전체를 돌 x, y와 경계의 길이가 될 d1과 d2를 구한다. 4중 for문을 사용한다.
for (int x = 1; x <= N; x++) {
for (int y = 1; y <= N; y++) {
for (int d1 = 1; d1 <= N; d1++) {
for (int d2 = 1; d2 <= N; d2++) {
//3~6번 실행
}
}
}
}//end 4 for
3. fifth 배열에 경계선을 true로 표시한다
문제에 주어진 경계선 조건은 다음과 같다
- (x, y), (x+1, y-1), ..., (x+d1, y-d1)
- (x, y), (x+1, y+1), ..., (x+d2, y+d2)
- (x+d1, y-d1), (x+d1+1, y-d1+1), ... (x+d1+d2, y-d1+d2)
- (x+d2, y+d2), (x+d2+1, y+d2-1), ..., (x+d2+d1, y+d2-d1)
이 경계선을 그대로 반복문에 적용한다.
for (int x = 1; x <= N; x++) {
for (int y = 1; y <= N; y++) {
for (int d1 = 1; d1 <= N; d1++) {
for (int d2 = 1; d2 <= N; d2++) {
//문제에 주어진 x, y, d1, d2의 조건
//(d1, d2 ≥ 1, 1 ≤ x < x+d1+d2 ≤ N, 1 ≤ y-d1 < y < y+d2 ≤ N)
if (x + d1 + d2 > N) continue;
if (y - d1 < 1 || y + d2 > N) continue;
popu = new int[5]; //1~5번 선거구의 인구수를 저장할 배열
//5번 선거구의 경계선을 지역구 배열에 true 표시
//각 지역구별로 다르게 범위를 구하면서 인구수 저장
boolean[][] fifth = new boolean[N + 1][N + 1];
//경계선 긋기
for (int r = x, c = y; r <= x + d1; r++, c--)
fifth[r][c] = true;
for (int r = x, c = y; r <= x + d2; r++, c++)
fifth[r][c] = true;
for (int r = x + d1, c = y - d1; r <= x + d1 + d2; r++, c++)
fifth[r][c] = true;
for (int r = x + d2, c = y + d2; r <= x + d2 + d1; r++, c--)
fifth[r][c] = true;
}
}
}
}//end 4 for

위의 코드를 실행하면 다음과 같은 배열이 만들어지고, true가 5번 선거구의 경계선이 된다.
처음에 r, c를 2중 for문을 사용했더니 □ 모양의 경계선이 만들어졌다. r과 c를 동시에 증감시켜야 ◇ 모양으로 경계선이 생성된다.
4. 1~4 선거구 범위에 맞춰 popu 배열에 각 선거구의 인구수를 저장한다
선거구 범위
- 1번 선거구: 1 ≤ r < x+d1, 1 ≤ c ≤ y
- 2번 선거구: 1 ≤ r ≤ x+d2, y < c ≤ N
- 3번 선거구: x+d1 ≤ r ≤ N, 1 ≤ c < y-d1+d2
- 4번 선거구: x+d2 < r ≤ N, y-d1+d2 ≤ c ≤ N

한 행씩 읽으면서 1~4구역을 선거구 범위에 맞춰 읽는다. 한 행을 읽는 도중 경계선을 만난다면 다음 행으로 넘어간다.
1번과 3번 구역은 좌->우로 읽으면 되지만 2번과 4번 구역은 화살표와 같이 열의 끝에서 앞으로 읽어야 경계선을 만날 수 있다.
//1번 선거구 인원수 더하기
for (int r = 1; r < x + d1; r++) {
for (int c = 1; c <= y; c++) {
if (fifth[r][c]) break; //경계선 만나면 다음줄로
popu[0] += map[r][c];
}
}
//2번 선거구
for (int r = 1; r <= x + d2; r++) {
for (int c = N; c >= y + 1; c--) {//열을 뒤에서부터 읽음
if (fifth[r][c]) break;
popu[1] += map[r][c];
}
}
//3번 선거구
for (int r = x + d1; r <= N; r++) {
for (int c = 1; c < y - d1 + d2; c++) {
if (fifth[r][c]) break;
popu[2] += map[r][c];
}
}
//4번 선거구
for (int r = x + d2 + 1; r <= N; r++) {
for (int c = N; c >= y - d1 + d2; c--) {//열을 뒤에서부터 읽음
if (fifth[r][c]) break;
popu[3] += map[r][c];
}
}
5. 5번 선거구 = 전체 인구수 - popu배열 전체 합
//5번 선거구 인원수 구하기
int tempSum = 0;
for (int k = 0; k < 4; k++) {
tempSum += popu[k];
}
popu[4] = totalPopu - tempSum;
6. popu 배열을 정렬해서 최대값과 최소값의 차이를 구해 전체 인구 차이의 최소값을 갱신한다
Arrays.sort(popu);
minPopu = Math.min(minPopu, (popu[4] - popu[0])); //최대-최소 인구수 가장 작은 값 갱신
전체 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main_17779_게리맨더링2 {
static int N, totalPopu;
static int[] popu; //인구수를 저장할 배열
static int[][] map;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
totalPopu = 0;
map = new int[N + 1][N + 1];
for (int i = 1; i <= N; i++) {
StringTokenizer tokens = new StringTokenizer(br.readLine());
for (int j = 1; j <= N; j++) {
map[i][j] = Integer.parseInt(tokens.nextToken());
totalPopu += map[i][j]; //총 인구수를 구함
}
}//end input
//(x, y) (dx, dy)를 정한다
//정한 조건에 따라 경계선을 표시한다
//1~4번 선거구를 구하고 popu 배열에 순서대로 넣는다
//5번 선거구이면 총인구수 - popu합을 빼서 5번 선거구 인구수로 넣어준다
//배열 정렬하고 (맨 뒤 - 맨 앞)을 구해서 최소값 갱신
int minPopu = Integer.MAX_VALUE;
for (int x = 1; x <= N; x++) {
for (int y = 1; y <= N; y++) {
for (int d1 = 1; d1 <= N; d1++) {
for (int d2 = 1; d2 <= N; d2++) {
//1. 의 d1, d2에 대한 길이 조건에 해당되지 않으면 탐색 안 함
if (x + d1 + d2 > N) continue;
if (y - d1 < 1 || y + d2 > N) continue;
popu = new int[5];
//5번 선거구의 경계선을 지역구 배열에 true 표시
//각 지역구별로 다르게 범위를 구하면서 인구수 저장
boolean[][] fifth = new boolean[N + 1][N + 1];
//경계선 긋기
for (int r = x, c = y; r <= x + d1; r++, c--)
fifth[r][c] = true;
for (int r = x, c = y; r <= x + d2; r++, c++)
fifth[r][c] = true;
for (int r = x + d1, c = y - d1; r <= x + d1 + d2; r++, c++)
fifth[r][c] = true;
for (int r = x + d2, c = y + d2; r <= x + d2 + d1; r++, c--)
fifth[r][c] = true;
//한줄씩 범위대로 탐색
//1번 선거구 인원수 더하기
for (int r = 1; r < x + d1; r++) {
for (int c = 1; c <= y; c++) {
if (fifth[r][c]) break; //경계선 만나면 다음줄로
popu[0] += map[r][c];
}
}
//2번 선거구
for (int r = 1; r <= x + d2; r++) {
for (int c = N; c >= y + 1; c--) {//열을 뒤에서부터 읽음
if (fifth[r][c]) break;
popu[1] += map[r][c];
}
}
//3번 선거구
for (int r = x + d1; r <= N; r++) {
for (int c = 1; c < y - d1 + d2; c++) {
if (fifth[r][c]) break;
popu[2] += map[r][c];
}
}
//4번 선거구
for (int r = x + d2 + 1; r <= N; r++) {
for (int c = N; c >= y - d1 + d2; c--) {//열을 뒤에서부터 읽음
if (fifth[r][c]) break;
popu[3] += map[r][c];
}
}
//인구수가 0인 곳이 있으면 계산 안하고 넘어감
if (popu[0] == 0 || popu[1] == 0 || popu[2] == 0 || popu[3] == 0) {
break;
}
//5번 선거구 인원수 구하기
int tempSum = 0;
for (int k = 0; k < 4; k++) {
tempSum += popu[k];
}
popu[4] = totalPopu - tempSum;
Arrays.sort(popu);
minPopu = Math.min(minPopu, (popu[4] - popu[0])); //최대-최소 인구수 가장 작은 값 갱신
}
}
}
}//end 4 for
System.out.println(minPopu);
}//end main
}
풀고 나니 어려운 문제가 아니었으나, 문제를 이해하는데 시간이 오래 걸렸다.
조건에 나온대로 for문의 범위를 정하면 금방 풀 수 있는 문제였다. 난 금방 못 풀었지만.
'Problem Solving > BOJ' 카테고리의 다른 글
| [백준 Java] 1946 신입 사원 (0) | 2023.03.16 |
|---|---|
| [백준 Java] 10162 전자레인지 (0) | 2023.03.16 |
| [백준 Java] 1789 수들의 합 (0) | 2023.03.15 |
| [백준 Java] 1916 최소비용 구하기 (0) | 2022.12.19 |
| [백준 Java] 1194 달이 차오른다 가자 (0) | 2022.10.14 |