【JAVA】每类各选一个后求最大最小值差值的最小值(Pro20210802)(优先级队列 / IndexTree
题目:
Lin 打算在自己住的村子里建 N 个不同的便利设施。每个便利设施有 K 种建造方法,设施的便利性因建造方法而异。因此,Lin 尝试从 K 种方法中各选择一种来建造这 N 个便利设施。
当 N 个便利设施中便利性最高和最低的差值越小,便利设施越便于使用。Lin 希望算出最小的差值。
假设便利设施数量 N 为 3,各设施的建造方法数量 K 为 4(如上 [图] 中所示)。如果便利设施 ① 和 ③ 的建成便利性为 1,设施 ② 的便利性为 5,那么上文所述的便利性差值便为 4。如果便利设施 ① 建成便利性为 15,设施 ② 便利性为 17,设施 ③ 便利性为 18,则差值为 3,这是差值最小的情况。
请帮助 Lin 算出上述的最小差值。
[限制条件]
- 便利设施数量 N 和建造方法数量 K 均为介于 2 到 100,000 之间的整数。
- N * K 不得超过 1,000,000。
- 各设施的便利性值为介于 1 到 1,000,000,000 之间的整数。
- 同一便利设施的建造方法(便利性值)不会重复。
[输入]
首先,给定测试用例数量 T,后面接着输入 T 种测试用例。在每个测试用例的第一行,给定便利设施类型数量 N 和建造方式数量 K,以空格分隔。接下来的 N 行,每行以升序给定 K 个便利性数值,以空格分隔。
[输出]
每个测试用例输出一行。首先,输出 “#x”(x 为测试用例编号,从 1 开始),加一个空格,然后输出文中提到的最小差值。
[输入和输出示例]
(输入)
3
3 4
1 10 15 24
5 8 17 20
1 6 12 18
3 5
1 6 12 26 35
3 9 28 33 35
10 25 29 30 31
3 5
1 20 37 46 1000000000
1 10 30 59 999999992
10 20 28 41 1000000000
(输出)
#1 3
#2 3
#3 8
思路:
暴力求解的话,从最左侧开始,需要计算每一种可能性。
但对于 ASW = MAX - MIN 来说,想要ASW 更小,只有两种操作,即:MAX减小,或MIN增大。
从最左侧开始的话,MAX不可能再减小了,就只能MIN增大。
对于每一行来说,已经是升序的了,那就是对于MIN所在的行,取下一个,再在每行所选的元素中取最大值和最小值。
代码(优先级队列):
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
public class Main {
static int T, N, K, ASW;
static int[][] DATA; // 原始数据
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210802.txt"));
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(bf.readLine());
T = Integer.parseInt(st.nextToken());
for (int testcase = 1; testcase <= T; testcase++) {
st = new StringTokenizer(bf.readLine());
N = Integer.parseInt(st.nextToken());
K = Integer.parseInt(st.nextToken());
DATA = new int[N][K];
ASW = Integer.MAX_VALUE;
// 读取数据
for (int row = 0; row < N; row++) {
st = new StringTokenizer(bf.readLine());
for (int column = 0; column < K; column++)
DATA[row][column] = Integer.parseInt(st.nextToken());
}
doCalc();
System.out.println("#" + testcase + " " + ASW);
}
}
public static void doCalc() {
PriorityQueue<Installations> pqMax = new PriorityQueue<Installations>(new Comparator<Installations>() {
@Override
public int compare(Installations o1, Installations o2) {
return o2.cost - o1.cost;
}
});
PriorityQueue<Installations> pqMin = new PriorityQueue<Installations>(new Comparator<Installations>() {
@Override
public int compare(Installations o1, Installations o2) {
return o1.cost - o2.cost;
}
});
// 从最左侧(最小值)开始
for (int row = 0; row < N; row++) {
pqMax.add(new Installations(DATA[row][0], row, 0));
pqMin.add(new Installations(DATA[row][0], row, 0));
}
// 不断移动最小值的指针求局部最优解
while (!pqMin.isEmpty()) {
Installations max = pqMax.peek(); // 最小值移动指针时,新添加的值,对求最大值没有影响
Installations min = pqMin.poll(); // 最小值移动指针时,最小值要更新(此处为删除)
ASW = Math.min(ASW, max.cost - min.cost);
if (min.column == K - 1)
break;
// 移动指针
pqMax.add(new Installations(DATA[min.row][min.column + 1], min.row, min.column + 1));
pqMin.add(new Installations(DATA[min.row][min.column + 1], min.row, min.column + 1));
}
}
}
class Installations {
int cost, row, column;
public Installations(int cost, int row, int column) {
this.row = row;
this.column = column;
this.cost = cost;
}
}
代码(IndexTree):
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int T, N, K, ASW, IDX;
static int[][] DATA; // 原始数据
static Installations[] MAX_TREE, MIN_TREE;
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210802.txt"));
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(bf.readLine());
T = Integer.parseInt(st.nextToken());
for (int testcase = 1; testcase <= T; testcase++) {
st = new StringTokenizer(bf.readLine());
N = Integer.parseInt(st.nextToken());
K = Integer.parseInt(st.nextToken());
IDX = 1;
while (IDX < N)
IDX *= 2;
DATA = new int[N][K];
ASW = Integer.MAX_VALUE;
MAX_TREE = new Installations[IDX * 2];
MIN_TREE = new Installations[IDX * 2];
// 读取数据
for (int row = 0; row < N; row++) {
st = new StringTokenizer(bf.readLine());
for (int column = 0; column < K; column++)
DATA[row][column] = Integer.parseInt(st.nextToken());
}
// 初始化Tree
for (int index = 1; index < 2 * IDX; index++) {
MAX_TREE[index] = new Installations(0, 0, 0);
MIN_TREE[index] = new Installations(-1000000001, 0, 0);
}
// 计算起点为每行选择最左侧的数
for (int row = 0; row < N; row++) {
update(MAX_TREE, row + IDX, new Installations(DATA[row][0], row, 0));
update(MIN_TREE, row + IDX, new Installations(-DATA[row][0], row, 0));
}
while (true) {
ASW = Math.min(ASW, MAX_TREE[1].cost + MIN_TREE[1].cost);
int row = MIN_TREE[1].row;
int column = MIN_TREE[1].column + 1;
if (column == K)
break;
// 更新最小值(移动最小值指针)
update(MAX_TREE, row + IDX, new Installations(DATA[row][column], row, column));
update(MIN_TREE, row + IDX, new Installations(-DATA[row][column], row, column));
}
System.out.println("#" + testcase + " " + ASW);
}
}
private static void update(Installations[] tree, int index, Installations obj) {
tree[index] = obj;
index /= 2;
while (index > 0) {
obj = tree[index * 2].cost > tree[index * 2 + 1].cost ? tree[index * 2] : tree[index * 2 + 1];
tree[index] = new Installations(obj.cost, obj.row, obj.column);
index /= 2;
}
}
static class Installations {
int cost, row, column;
public Installations(int cost, int row, int column) {
this.row = row;
this.column = column;
this.cost = cost;
}
}
}