【JAVA】每类各选一个后求最大最小值差值的最小值(Pro20210802)(优先级队列 / IndexTree)

本文介绍了如何帮助Lin计算在不同建造方法下,N个便利设施的最大最小值差值的最小值问题。利用优先级队列和索引树的算法思路,解决大规模数据的高效计算,给出具体代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【JAVA】每类各选一个后求最大最小值差值的最小值(Pro20210802)(优先级队列 / IndexTree

题目:

Lin 打算在自己住的村子里建 N 个不同的便利设施。每个便利设施有 K 种建造方法,设施的便利性因建造方法而异。因此,Lin 尝试从 K 种方法中各选择一种来建造这 N 个便利设施。
当 N 个便利设施中便利性最高和最低的差值越小,便利设施越便于使用。Lin 希望算出最小的差值。

[图]

假设便利设施数量 N 为 3,各设施的建造方法数量 K 为 4(如上 [图] 中所示)。如果便利设施 ① 和 ③ 的建成便利性为 1,设施 ② 的便利性为 5,那么上文所述的便利性差值便为 4。如果便利设施 ① 建成便利性为 15,设施 ② 便利性为 17,设施 ③ 便利性为 18,则差值为 3,这是差值最小的情况。

请帮助 Lin 算出上述的最小差值。

[限制条件]

  1. 便利设施数量 N 和建造方法数量 K 均为介于 2 到 100,000 之间的整数。
  2. N * K 不得超过 1,000,000。
  3. 各设施的便利性值为介于 1 到 1,000,000,000 之间的整数。
  4. 同一便利设施的建造方法(便利性值)不会重复。

[输入]
首先,给定测试用例数量 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;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值