#P2376. 第1题-小明的数据
-
1000ms
Tried: 898
Accepted: 179
Difficulty: 3
所属公司 :
华为
时间 :2023年5月31日-暑期实习
-
算法标签>优先队列
第1题-小明的数据
思路:大根堆+模拟
给定 M 个数据,每个数据为 A 个重复的 B 。要求每读入一次数据,最多输出当前已读数据中最大的 N 个值。
为了能够快速得到最大的值,我们可以使用大根堆来对数据进行存储。
什么是大根堆?
其学名是优先队列。具体表现形式(或者说我们自己手动模拟的形式)是一颗二叉树,每个结点最多有两个儿子,且对于任意一个结点,都符合:该结点任意儿子的值,都比该结点的值小。因此,堆顶(或者说二叉树的根节点)就是所有数中最大的值。
对于C++选手来说,刚好有STL能够使用,即priority_queue,其默认是大根堆。比较基础的几个函数分别是:
pop(),弹出堆顶,并返回该元素push(x),将 x 加入到堆中size(),返回堆中当前元素的个数clear(),清空堆
其他语言也有相应的封装包,比如python里有heapq。因此如果自己不想动手实现一个优先队列的话,可以直接使用封装包。
由于该题的数据量很小,我们可以直接借助大根堆来模拟这个过程。
具体过程如下:
- 读入一行数据,将其加入到大根堆
q中 - 从大根堆不断取出堆顶,进行输出,并将该数据放入到临时队列
tmp中,直到输出一共 N 个数字 - 将
tmp中的数据再重新放回堆中
由于 N 很小,最大仅为24,也就是说每次最多输出24个字符,而堆的每次操作都可以近似看作 O(logM) 的,最坏情况下每次取出堆中24个数据(每个数据只重复1次)输出再放回去也就是 O(NlogM) 的时间复杂度。读入 M 行数据,总共要输出 M 次,因此总时间复杂度为 O(NMlogM)。
代码
C++
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n,m;
struct node{
int cnt;// 重复多少次
int val;// 值
};
queue<node> tmp;
priority_queue<node> q;
bool operator <(const node a,const node b){// 重载运算符
return a.val<b.val;
}
int main(){
scanf("%d %d",&n,&m);
node a;
for(int i=1,rest;i<=m;++i){
scanf("%d %d",&a.cnt,&a.val);
q.push(a);
rest=n;// rest为输出某一字符后,剩余的长度,初始为n
while(rest>0){
if(!q.size()) break;// 大根堆里已经没有字符了,直接break
// 取出堆顶
a=q.top();
q.pop();
// 输出 min(rest,a.cnt) 次
for(int i=1;i<=min(rest,a.cnt);++i){
printf("%d",a.val);
}
// 从堆里取出来放到临时队列中
tmp.push(a);
rest-=a.cnt;
}
while(tmp.size()){// 将队列中的数据重新放回堆中
q.push(tmp.front());
tmp.pop();
}
printf("\n");
}
return 0;
}
python
import heapq
class Node:
def __init__(self, cnt, val):
self.cnt = cnt
self.val = val
def __lt__(self, other):
return self.val > other.val
n, m = map(int, input().split())
q = []
tmp = []
for _ in range(m):
cnt, val = map(int, input().split())
heapq.heappush(q, Node(cnt, val))
rest = n # rest为输出某一字符后,剩余的长度,初始为n
while rest > 0:
if not q:
break # 大根堆里已经没有字符了,直接break
# 取出堆顶
a = heapq.heappop(q)
# 输出 min(rest,a.cnt) 次
for _ in range(min(rest, a.cnt)):
print(a.val, end="")
# 从堆里取出来放到临时队列中
tmp.append(a)
rest -= a.cnt
while tmp:
# 将队列中的数据重新放回堆中
heapq.heappush(q, tmp.pop(0))
print()
Java
import java.util.*;
import java.io.*;
public class Main {
// 定义节点类,包含重复次数和值
static class Node {
long cnt; // 重复次数
int val; // 值
Node(long cnt, int val) {
this.cnt = cnt;
this.val = val;
}
}
public static void main(String[] args) throws IOException {
// 使用BufferedReader提高输入效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] first = br.readLine().trim().split("\\s+");
int N = Integer.parseInt(first[0]); // 最大保留个数
int M = Integer.parseInt(first[1]); // 输入行数
// 定义大根堆,按照val从大到小排序
PriorityQueue<Node> pq = new PriorityQueue<>(new Comparator<Node>() {
public int compare(Node a, Node b) {
return b.val - a.val; // 大根堆
}
});
// 临时队列,用于存储当前批次取出的节点
Queue<Node> tmp = new LinkedList<>();
// 处理每一批输入
for(int i=0;i<M;i++) {
String[] line = br.readLine().trim().split("\\s+");
long A = Long.parseLong(line[0]); // 重复次数
int B = Integer.parseInt(line[1]); // 值
pq.offer(new Node(A, B)); // 将节点加入优先级队列
long rest = N; // 剩余需要取的个数
StringBuilder sb = new StringBuilder(); // 存储当前输出
// 取出当前最大的N个元素
while(rest > 0 && !pq.isEmpty()) {
Node current = pq.poll(); // 取出当前最大值
long k = Math.min(rest, current.cnt); // 取出次数不超过剩余需要的个数
// 将当前值k次添加到输出中
for(int j=0; j<k; j++) {
sb.append(current.val);
}
tmp.offer(current); // 将当前节点暂存
rest -= k; // 更新剩余需要的个数
}
// 将暂存的节点重新加入优先级队列
while(!tmp.isEmpty()) {
pq.offer(tmp.poll());
}
// 输出当前队列的结果
System.out.println(sb.toString());
}
}
}
Go
package main
import (
"container/heap"
"fmt"
)
type Node struct {
cnt int // 重复多少次
val int // 值
}
type PriorityQueue []Node
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].val < pq[j].val
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue) Push(x interface{}) {
item := x.(Node)
*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
*pq = old[0 : n-1]
return item
}
func main() {
var n, m int
fmt.Scan(&n, &m)
q := make(PriorityQueue, 0)
tmp := make(PriorityQueue, 0)
for i := 0; i < m; i++ {
var a Node
fmt.Scan(&a.cnt, &a.val)
heap.Push(&q, a)
rest := n // rest为输出某一字符后,剩余的长度,初始为n
for rest > 0 {
if len(q) == 0 {
break // 大根堆里已经没有字符了,直接break
}
// 取出堆顶
a := heap.Pop(&q).(Node)
// 输出 min(rest,a.cnt) 次
for j := 0; j < min(rest, a.cnt); j++ {
fmt.Print(a.val)
}
// 从堆里取出来放到临时队列中
heap.Push(&tmp, a)
rest -= a.cnt
}
for len(tmp) > 0 {
// 将队列中的数据重新放回堆中
heap.Push(&q, heap.Pop(&tmp).(Node))
}
fmt.Println()
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
Js
const readline = require('readline');
function min(a, b) {
return a < b ? a : b;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let n, m;
const q = [];
const tmp = [];
rl.on('line', (line) => {
const input = line.split(' ').map(Number);
if (!n) {
[n, m] = input;
return;
}
const [cnt, val] = input;
q.push({ cnt, val });
let rest = n; // rest为输出某一字符后,剩余的长度,初始为n
while (rest > 0) {
if (q.length === 0) break; // 大根堆里已经没有字符了,直接break
// 取出堆顶
const a = q.shift();
// 输出 min(rest,a.cnt) 次
for (let i = 0; i < min(rest, a.cnt); i++) {
process.stdout.write(String(a.val));
}
// 从堆里取出来放到临时队列中
tmp.push(a);
rest -= a.cnt;
}
while (tmp.length > 0) {
// 将队列中的数据重新放回堆中
q.push(tmp.shift());
}
process.stdout.write('\n');
});
题目内容
小明面临一个挑战,需要从海量的网络数据中筛选出繁忙时段的数据。考虑到数据规模庞大,小明无法对所有数据进行排序,然后选择前N个最大值作为繁忙时段的数据。聪明的小明想到了使用一个固定大小的优先级队列来筛选数据。为了简化场景,我们将海量网络数据表示为一个正整数集合,并且仅需选择N个最大的正整数作为结果。对于每批输入数据,小明将输出一行结果,将这N个正整数按照顺序拼接成一行字符串进行输出。
输入描述
输入第一行为正整数N和M,N为忙时个数,M为输入的数据行数,(1 ≤ N ≤ 24 ,1 ≤ M ≤ 1000)
接下来输入M行,每行两个正整数A,B,以空格分隔,表示有A个重复的正整数B,1 ≤ A,B ≤ 2147483647 。
输出描述
输出每增加一批数据对应的队列结果,直接将队列里的所有数据集从大到小拼接成字符串输出.
样例1
输入
24 3
11 5
2 1
18 7
输出
55555555555
5555555555511
777777777777777777555555
解释
保留24个忙时数据
第一次输入11个5,则队列输出为55555555555
第二次输入2个1,则队列输出为5555555555511
第三次输入18个7,则队列输出为777777777777777777555555