#문제
레벨: G2
알고리즘: 투포인터
풀이시간: 1시간
힌트 참조 유무: 유
https://www.acmicpc.net/problem/7453
#문제 풀이
✔ 풀이법
개의 배열을 각각 조회하면 시간초과가 발생하므로 와 원소의 합이 와 원소의 합이 부호만 다르고 절대값이 같은 경우를 찾으면된다.여기서 의아한 사람들이 있을 거다. 엥 a+b = - (c+d) 조합만 고려하면 모든 경우의 수를 찾지 못하지 않나? a+c = -(b+d)라던지 a+d = -(c+b) 의 조합을 고려해야지 않나?
아니다. 한 가지의 조합만 고려해도 모든 경우의 수를 알 수 있다. 예시를 들어보겠다.
위와 같은 배열이 있을 때 a+b = - (c+d)의 경우의 수만 구해보겠다.
a+b가 (1+4=5), (1+5=6), (2+4=6), (2+5=7)의 경우의 수가 있다.그럼 c+d의 합이 a+b의 -를 찾아야 하니 -5,-6,-7의 경우의 수를 찾아야 한다.c+d가 -5가 되는 경우의 수 { (-1,-4) }
c+d가 -6가 되는 경우의 수 { (-1,-5), (-2,-4)}
c+d가 -7가 되는 경우의 수 { (-2, -5) }
가 이렇게 된다.
정답은
a+b가 5가 되는 경우의 수 x c+d가 -5가 되는 경우의 수
+
a+b가 6이 되는 경우의 수 x c+d가 -6이 되는 경우의 수
+
a+b가 7이 되는 경우의 수 x c+d가 -7이 되는 경우의 수
이므로
(1x1) + (2 x 2) + (1 x 1) = 6이다.
이제
a+c = -(b+d)의 경웅의 수를 살펴 위와 같다는 것을 증명하겠다.
a+c의 경우의 수는 { (1,-1), (1, -2), (2,-1), (2,-2) } 인 경우의 수가 있다.
그럼 b+d의 합이 a+c의 -를 찾아야 하니 0,1,-1의 경우의 수를 찾아야 한다.
b+d 가 0이 되는 경우의 수 { (4,-4) , (5,-5)}
b+d 가 1이 되는 경우의 수 { (5,-4)}
b+d 가 -1이 되는 경우의 수 { (4, -5) }
가 된다.
a+c 가 0이 되는 경우의 수 x b+d 가 0이 되는 경우의 수
+
a+c 가 1이 되는 경우의 수 x b+d 가 -1이 되는 경우의 수
+
a+c 가 -1가 되는 경우의 수 x b+d 가 1이 되는 경우의 수
이므로
(2x2) + (1 x 1) + (1 x 1) = 6이다.
똑같이 6으로 똑같다. 에이.. 그러면 둘이 구했던 경우의 수가 다르니 정답이 6으로 같은 게 아니라 6 + 6 해야 되는 거 아니야? 아니다. 구했던 조합을 살펴보자. (파란색이 왼쪽 조합이다.)
AB CD일 때
(1,4,-1,-4) (1,5,-1,-5) (1,5,-2,-4), (2,4,-1,-5) (2,4,-2,-4) (2,5,-2,-5)이다.
AC BD일 때는
(1,4,-1,-4) (1,5,-1,-5) (1,5,-2,-4), (2,4,-1,-5) (2,4,-2,-4) (2,5,-2,-5)이다.
보시다시피 조합을 달리해서 구하더라도 결과값은 같다.
그래서 우리는 A+B = -(C+D) 조합만 구할 것이다.
☝🏻 해시 맵
원소의 합이 key, 합의 개수가 value인 맵을 만든다. 와 원소의 합에 을 곱한 값을 와 원소의 합에서 찾고 각 개수를 곱해준다.
하지만 시간초과가 발생했다.
✌🏻 이분 탐색
와 원소의 합에 을 곱한 값을 와 원소 합 배열에서 찾는다. 중복이 가능하므로 upper bound와 lower bound를 사용하여 해당 개수를 더한다.
🤟🏻 투 포인터
두 개의 포인터 중 하나는 왼쪽에서, 다른 하나는 오른쪽에서 시작한다.
, 합 배열의 원소와 , 합 배열의 원소를 더해 이 되는 곳을 찾는다.
원소의 중복이 존재하기 때문에 두 배열에서 현재 원소와 동일한 값을 갖는 원소 개수를 센 뒤 두 값을 곱한다.
#풀이 코드
투 포인터 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class Main {
static int n;
static long[] A;
static long[] B;
static long[] C;
static long[] D;
static long findByTwoPointer() {
long[] AB = new long[n * n];
long[] CD = new long[n * n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
AB[i * n + j] = A[i] + B[j];
CD[i * n + j] = C[i] + D[j];
}
}
Arrays.sort(AB);
Arrays.sort(CD);
long count = 0;
//2개의 배열에 4개의 포인터(AB왼,AB오,CD왼,CD오)를 쓰는 게 아니라
//2개의 배열에 2개의 포인터(AB왼,CD오)를 쓰는 거다
int l = 0, r = n * n - 1;
while (l < n * n && r >= 0) {
long sum = AB[l] + CD[r];
if (sum < 0) {
l++;
continue;
}
if (sum > 0) {
r--;
continue;
}
long lCount = 1;
l++;
//아래는 AB = -CD인 경우이다.
//AB조합에 같은 숫자가 있다면 그 수만큼 CD조합이랑 곱해줘야 하므로
//동일한 숫자를 카운트 하며 포인터를 옮긴다.
while (l < n * n && AB[l] == AB[l - 1]) {
lCount++;
l++;
}
//CD조합에 같은 숫자가 있다면 그 수만큼 AB조합이랑 곱해줘야 하므로
//동일한 숫자를 카운트 하며 포인터를 옮긴다.
long rCount = 1;
r--;
while (r >= 0 && CD[r] == CD[r + 1]) {
rCount++;
r--;
}
count += lCount * rCount;
}
return count;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
n = Integer.parseInt(br.readLine());
A = new long[n];
B = new long[n];
C = new long[n];
D = new long[n];
for (int i = 0; i < n; i++) {
st = new StringTokenizer(br.readLine());
A[i] = Long.parseLong(st.nextToken());
B[i] = Long.parseLong(st.nextToken());
C[i] = Long.parseLong(st.nextToken());
D[i] = Long.parseLong(st.nextToken());
}
System.out.println(findByTwoPointer());
br.close();
}
}
'알고리즘 > 투포인터' 카테고리의 다른 글
[백준 2407] 두 용액 / 자바 / 투 포인터 (0) | 2024.06.17 |
---|---|
[백준 1644] 소수의 연속합 (0) | 2024.06.16 |
[백준 11728] 배열 합치기 / 자바 /투 포인터 (1) | 2024.06.14 |