POJ No.2758-4 Values Whose Sum is 0(折半枚举)
给定各有n个整数的四个数列A、B、C、D。要从每个数列中各取出1个数,使四个数的和为0.求出这样的组合的个数。当一个数列中有多个相同的数字时,把它们作为不同的数字看待。
限制条件
1<=n<=4000
|{数字的值)|<2^28
输入
N = 6
A={-45,-41,-36,-36,26,-32}
B={22,-27,53,30,-38,-54}
C={42,56,-37,-75,-10,-6}
D={-16,30,77,-46,62,45}
输出
5
(-45-27+42+30=0, 26+30-10-46=0, -32+22+56-46=0,-32+30-75+77=0, -32-54+56+30=0)
分析:从4个数列中选择的话总共有n^4种情况,所以全部判断一遍不可行。不过把他们对半分成AB和CD在考虑,这时候就可以快速确定。
从2个数列选择的话,只有n^2种组合,所以可以进行枚举。先从A、B中取出a、b后,为了使总和为0,需要从C、D中取出c+d=-(a+b)。因此先将C、D种取出数字的n^2种方法全部枚举,将这些和排好序,然后就可以用二分搜索。
这个算法的复杂度是O(n^2logn)
有时候,问题的规模较大,无法枚举所有元素的组合,但能够枚举一半元素的组合。此时,将问题拆成两半后分别枚举,在合并他们的结果,这种方法叫做折半枚举。
import java.util.Arrays;
import java.util.Scanner;
public class Algorithm {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
System.out.print("n=");
int n = cin.nextInt();
int[] A = new int[n];
int[] B = new int[n];
int[] C = new int[n];
int[] D = new int[n];
int[] CD = new int[n * n];
input(n, A, B, C, D);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
CD[i * n + j] = C[i] + D[j];
Arrays.sort(CD);
int res = 0;
int cd;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
cd = -(A[i] + B[j]);
if (binarySearch(CD, cd))
res++;
}
System.out.println(res);
}
static void input(int n, int[] A, int[] B, int[] C, int[] D) {
Scanner cin = new Scanner(System.in);
System.out.print("A=");
for (int i = 0; i < n; i++)
A[i] = cin.nextInt();
System.out.print("B=");
for (int i = 0; i < n; i++)
B[i] = cin.nextInt();
System.out.print("C=");
for (int i = 0; i < n; i++)
C[i] = cin.nextInt();
System.out.print("D=");
for (int i = 0; i < n; i++)
D[i] = cin.nextInt();
}
public static boolean binarySearch(int[] CD, int key) {
int low = 0;
int high = CD.length - 1;
while (low <= high) {
int middle = (low + high) / 2;
if (key == CD[middle]) {
return true;
} else if (key < CD[middle]) {
high = middle - 1;
} else {
low = middle + 1;
}
}
return false;
}
}