【JAVA】求数组前K个数中固定位置元素之和Pro20210524
题目
用户用餐后会在外卖应用上对食物的满意度进行评价。(满意度分数始终大于0)
研究满意度的专家称,当把满意度分数按照从小到大的顺序排列时,某个(X/Y)位置的满意度非常重要。因此作为管理满意度的负责人丽雅,她想逐个接收分数后进行处理。
每当接收的满意度为Y的倍数时,会调查X/Y位置的满意度并进行记录。(如果X=3,Y=4的情况,满意度有4个时取第三个分数,有八个时取第六个分数。)
然而,有时候用户也会删除他们的满意度分数。这种情况,为了方便会把该满意度分数接收为负数,然后删除该分数。(确定删除只会发生在已存在的分数上。)然而删除后,数据个数重新变成Y的倍数时,也仍然需要调查满意度分数并进行记录。
让我们思考一下 X=3,Y=4,并且按照 7、5、3、1、-1、7、7、5、6、1、-7、1 的顺序接收12个满意度分数的情况。
首先按照7、5、3、1的顺序输入时,输入的数据个数满足Y的倍数,所以按升序排列时,会成为[图1],X/Y位置的满意度分数是5。
接着输入了-1时,会如上[图2]一样,前面的1会被删除(请注意,负数值本身不是输入值,而是要删除的值), 然后输入7,会成为[图3]。此时,数据个数重新会变成Y的倍数,X/Y位置的满意度值为7。
然后输入7、5、6、1时,数据会成为Y的倍数,会有8个值按从小到大排序,此时跟[图4]一样,这时X/Y位置的值为7。
最后输入-7、1时,跟上述的条件一样,会删除一个跟-7的绝对值相同的7,然后添加1后,数据个数又会变成Y的倍数,成为[图 5]。此时的X/Y位置的值为6,所以符合条件的位置上所在的满意度分数之和为25(5+7+7+6)。
求出丽雅记录的满意度之和。
[限制条件]
1.输入的满意度分数的个数N为介于4到300,000之间的整数。
2.满意度分数为介于-1,000,000,000 到 1,000,000,000 之间的整数,分数为整数时,表示用户输入的满意度,分数为负数时,表示用户删除的满意度。不会给出0。
3.删除时,存在多个相同的满意度绝对值时,只删除其中一个满意度。
4.不会删除不存在的满意度。
5.X和Y是介于1到N的整数,且必须为互质数。
6.X小于Y。
[输入]
首先给出测试用例数量T,接着给出T种测试用例。每个测试用例的第一行给出数据个数N,以及空格区分给出表示比率的X,Y。第二行空格区分给出N个数字。
[输出]
每个测试用例输出一行。各测试用例输出#x(x是测试用例的编号,从1开始),加一个空格,输出丽雅调查的满意度之和。
[输入输出 示例]
(输入)
3
12 3 4
7 5 3 1 -1 7 7 5 6 1 -7 1
6 2 3
3 -3 2 4 4 1
9 2 3
7 2 6 2 7 7 3 3 7
(输出)
#1 25
#2 4
#3 20
方法一:优先级队列
思路:
维护两个优先级队列,一个降序,一个升序;
确保在有效元素的个数P是Y的倍数时,前 (P / Y) * X 个在升序的PQ中,后 P - (P / Y) * X 个在降序的PQ中;
添加数字时,优先往降序的PQ中添加(即:当 P / Y < X 时,往降序的PQ中添加;当 P / Y >= X 时,往升序的PQ中添加);
出现要删除的数D时,确认这个数在哪个PQ中(与降序PQ的有效元素中的最大值M比较,当 D <= M时,D在降序PQ中,反之则在升序PQ中),优先从降序的PQ中删除(不是真删除,需要一个集合,记录某个元素删除的次数);
当效元素的个数P是Y的倍数时,从降序PQ中获取最大有效元素(若获取的最大值,被删除的次数大于0时,重新获取,并将该元素的删除次数减一)累加为SUM;
SUM即为所求。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
public class Solution {
static HashMap<Integer, Integer> hm;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int T = Integer.parseInt(st.nextToken());
for (int t = 1; t <= T; t++) {
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int X = Integer.parseInt(st.nextToken());
int Y = Integer.parseInt(st.nextToken());
PriorityQueue<Integer> pqL = new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
PriorityQueue<Integer> pqR = new PriorityQueue<Integer>();
pqL.add(0);
pqR.add(20_0000_0001);
int pqlSize = 0, pqrSize = 0, value = 0, leftMax, rightMin;
long result = 0L;
hm = new HashMap<Integer, Integer>();
st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
value = Integer.parseInt(st.nextToken());
leftMax = getEffectiveValue(pqL);
rightMin = getEffectiveValue(pqR);
if (value > 0) {
if ((pqlSize + pqrSize) % Y < X) {
if (value <= leftMax) {
pqL.add(value);
pqL.add(leftMax);
pqR.add(rightMin);
} else {
pqL.add(Math.min(value, rightMin));
pqR.add(Math.max(value, rightMin));
pqL.add(leftMax);
}
pqlSize++;
} else {
pqL.add(Math.min(value, leftMax));
pqR.add(Math.max(value, leftMax));
pqR.add(rightMin);
pqrSize++;
}
} else {
int count = 1;
if (hm.get(-value) != null && hm.get(-value) > 0) {
count = hm.get(-value) + 1;
}
hm.put(-value, count);
if ((pqlSize + pqrSize - 1) % Y < X) {
pqlSize--;
if (-value > leftMax) {
pqR.add(leftMax);
} else {
pqL.add(leftMax);
}
pqR.add(rightMin);
} else {
pqrSize--;
if (-value <= leftMax) {
pqL.add(rightMin);
} else {
pqR.add(rightMin);
}
pqL.add(leftMax);
}
}
if ((pqlSize + pqrSize) > 0 && (pqlSize + pqrSize) % Y == 0) {
leftMax = getEffectiveValue(pqL);
result += (long) leftMax;
pqL.add(leftMax);
}
}
System.out.println("#" + t + " " + result);
}
br.close();
}
private static int getEffectiveValue(PriorityQueue<Integer> q) {
int value = q.poll();
while (hm.get(value) != null && hm.get(value) > 0) {
hm.put(value, hm.get(value) - 1);
value = q.poll();
}
return value;
}
}
方法二:IndexTree
思路:
将分数中的正值(或将负值取绝对值)散列化,则元素取值范围为 (0, 300000);
使用IndexTree的叶节点和元素值对应,按原数组元素(散列化后的值)顺序读取并处理;
添加数字时,为该数字对应的叶节点加1(同时更新其父节点);
出现要删除的数D时,该数字对应的叶节点减1(同时更新其父节点);
当效元素的个数P是Y的倍数时,求出第(P / Y) * X个元素所在的叶节点对应的值(散列化后的值),再找到原数组中对应的值(散列化前的值),累加为SUM;
SUM即为所求。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
static int N, X, Y, IDX = 524288;
static int[] DATA, COPY, TREE;
public static void main(String[] args) throws IOException {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210524.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int T = Integer.parseInt(st.nextToken());
for (int t = 1; t <= T; t++) {
st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
X = Integer.parseInt(st.nextToken());
Y = Integer.parseInt(st.nextToken());
DATA = new int[N];
COPY = new int[N];
TREE = new int[IDX * 2];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
DATA[i] = Integer.parseInt(st.nextToken());
COPY[i] = DATA[i];
}
Arrays.sort(COPY);
long ans = 0;
for (int i = 0; i < N; i++) {
int tmp = Arrays.binarySearch(COPY, Math.abs(DATA[i]));
if (DATA[i] < 0) {
update(IDX + tmp, -1);
} else {
update(IDX + tmp, 1);
}
if (TREE[1] > 0 && TREE[1] % Y == 0) {
ans += COPY[getTargetIndex(TREE[1] / Y * X) - IDX];
}
}
System.out.println("#" + t + " " + ans);
}
}
static void update(int i, int val) {
while (i > 0) {
TREE[i] += val;
i = i >> 1;
}
}
static int getTargetIndex(int count) {
int index = 1;
while (index < IDX) {
index *= 2;
if (TREE[index] < count) {
count -= TREE[index];
index++;
}
}
return index;
}
}