input은 S(1 ≤ S ≤ 4,294,967,295)으로 40억이 넘어가기 때문에 long으로 사용해야 한다.
1부터 더해야 하기 때문에 idx를 1로 시작한다.
200을 예로 들었을 때, 아래의 코드는 sum = 210, idx = 21인 상태에서 if문에 걸리게 된다.
idx++이 된 부분에 대해서 한번 빼주고, sum이 초과되는 부분에서 한번 더 빼준다.
설명으로 하니 복잡한데 반복문 내에 idx와 sum을 찍어보면 아래와 같이 나온다.
//idx sum
1 1
2 3
3 6
...
19 190
20 210
나가!
19
후위연산자로 증가한 값을 하나 줄여주고, 반복문 내에서 sum이 초과한 이전의 숫자를 사용하기 위해 한번 더 빼준다.
따라서 idx를 -2 해주게 되는 것이다.
처음에 이것을 잡는데 시간이 오래 걸렸다.
그 외의 반례로는 input 1, output 1을 확인하기.
코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main{
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
long S = Long.parseLong(br.readLine());
//1부터 계속 더하면서 S가 넘지 않을때까지 반복
long sum = 0;
long idx = 1;
while(true){
//System.out.print(idx +" ");
sum += idx++;
//System.out.println(sum);
if(sum > S){
//System.out.println("나가!");
idx-=2;
break;
}
}
System.out.println(idx);
}
}
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
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문의 범위를 정하면 금방 풀 수 있는 문제였다. 난 금방 못 풀었지만.