【JAVA】最大间隔限定的最大和(Pro20210827)(DP(IndexTree / PriorityQueue)
题目
Lia 在一个景点看到了由 N 个垫脚石组成的石桥。单纯地走过这些垫脚石感觉没有意思,所以她给每块垫脚石设置了一个分数,将踩到的所有石头上的分数全部加起来,想要得到一个最大分数。
但是,为了让这件事更有意思,她决定按下列规则来过桥。
- 第一块和最后一块垫脚石必须要踩。
- 设定一个K值,下一块踩的石头与上一块踩的石头间相隔的距离最大为 K。
- 不能重复踩同一块垫脚石。
假设 N 是 7,每个石头的分数如上图所示。如果 K 是 2,按 ① → ② → ④ → ⑥ → ⑦ 的方式移动可以获得最高分数,分数是 1 (-6 + -4 + 4 + -2 + 9)。如果 K 是 3,按 ① → ④ → ⑦ 的方式移动可以获得最高分数,分数是 7 (-6 + 4 + 9)。
请帮助 Lia 计算出按规则踩着垫脚石过桥时,可以获得的最高分数。
[限制条件]
- 组成桥的垫脚石数量 N 为介于 1 到 300,000 之间的整数。
- 每个垫脚石的分数在带符号的 32 位整数值范围内。
- K 为介于 1 到 N 之间的整数。
[输入]
首先,给定测试用例数量 T,后面接着输入 T 种测试用例。在每个测试用例的第一行,给定垫脚石数量 N 和距离 K,以空格分隔。在下一行,给定从 1 到 N 号垫脚石每个的分数,以空格分隔。
[输出]
每行输出一个测试用例。首先,输出“#x”(其中 x 表示测试用例编号,从 1 开始)加上一个空格,然后输出问题中要求的值。
[输入和输出示例]
(输入)
3
7 2
-6 -4 -6 4 -4 -2 9
7 3
-6 -4 -6 4 -4 -2 9
10 2
-3 -4 -2 -1 -7 -5 -9 -4 -7 -5
(输出)
#1 1
#2 7
#3 -20
思路
对于某个石头n,可以跳到这个石头的石头的下标,一定在区间 [n - M, n - 1] 范围内。
即:f(n) = data[n] + Max(f(n-M), f(n-M+1), f(n-M+2), …, f(n-1))
Max(f(n-M), f(n-M+1), f(n-M+2), …, f(n-1)) 即为连续区间最大值,可以使用 IndexTree 或 PriorityQueue。
代码(IndexTree)
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
static int T, N, M;
static long ASW[];
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210827.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine());
for (int tc = 1; tc <= T; tc++) {
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken()); // 石头数量
M = Integer.parseInt(st.nextToken()); // 跳跃间隔
int idx = 1; // Tree第一个叶节点下标
while (idx < N)
idx *= 2;
ASW = new long[2 * idx]; // Index Tree
Arrays.fill(ASW, Long.MIN_VALUE);
st = new StringTokenizer(br.readLine());
// 第一个石头必须踩
long score = Long.parseLong(st.nextToken());
update(idx, score);
// f(n) = data[n] + Max(f(n-M), f(n-M+1), f(n-M+2), ..., f(n-1))
for (int s, e, i = 1; i < N; i++) {
s = idx + Math.max(i - M, 0); // 必须确保s在叶节点层(即:s >= idx)
e = i + idx - 1;
score = query(s, e) + Long.parseLong(st.nextToken());
update(i + idx, score);
}
System.out.printf("#%d %d\n", tc, score);
}
}
private static long query(int sIdx, int eIdx) {
long result = Long.MIN_VALUE;
while (sIdx <= eIdx) {
if (sIdx % 2 > 0)
result = Math.max(result, ASW[sIdx]);
if (eIdx % 2 == 0)
result = Math.max(result, ASW[eIdx]);
sIdx = (sIdx + 1) / 2;
eIdx = (eIdx - 1) / 2;
}
return result;
}
private static void update(int idx, long value) {
while (idx > 0) {
ASW[idx] = Math.max(value, ASW[idx]);
idx /= 2;
}
}
}
代码(PriorityQueue)
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
public class Main {
static int T, N, M, ASW;
static PriorityQueue<Stone> pq = new PriorityQueue<Stone>((o1, o2) -> (o2.score - o1.score) > 0 ? 1 : -1);
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210827.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine());
for (int tc = 1; tc <= T; tc++) {
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken()); // 石头数量
M = Integer.parseInt(st.nextToken()); // 跳跃间隔
pq.clear();
st = new StringTokenizer(br.readLine());
// 第一个石头必须踩
long score = Long.parseLong(st.nextToken());
pq.add(new Stone(1, score));
// f(n) = data[n] + Max(f(n-M), f(n-M+1), f(n-M+2), ..., f(n-1))
for (int i = 2; i <= N; i++) {
score = getMax(i).score + Long.parseLong(st.nextToken());
pq.add(new Stone(i, score));
}
System.out.printf("#%d %d\n", tc, score);
}
}
/**
* @param i 当前是第几块石头
* @return 在间隔范围内的最大分值的石头(可以跳到当前石头上的石头中,最大分值的石头)
*/
private static Stone getMax(int i) {
while (pq.peek().idx < i - M) // 不能间隔大于M的,略去
pq.poll();
return pq.peek();
}
}
class Stone {
int idx;
long score;
public Stone(int idx, long score) {
this.idx = idx;
this.score = score;
}
}