华为OD机试 - 工单调度策略 - 并查集(Python/JS/C/C++ 2024 E卷 200分)

在这里插入图片描述

华为OD机试 2024E卷题库疯狂收录中,刷题点这里

专栏导读

本专栏收录于《华为OD机试真题(Python/JS/C/C++)》

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

一、题目描述

当小区通信设备上报警时,系统会自动生成待处理的工单,华为工单调度系统需要根据不同的策略,调度外线工程师(FME)上站修复工单对应的问题。 根据与运营商签订的合同,不同严重程度的工单被处理的时长要求不同,这个要求被称为我们称之为SLA时间。 假设华为和运营商商签有了运维合同,需要了每有一个外线工程师(FMEQ),每个工单根据问题严重度会给一个评分,如果SLA时间内完成修复的工单,华为获得工单评分奖励,超时SLA完成的工单不获得分,但必须完成该工单。运营商终会根据评分对华为进行结算。 请设计一种调度策略,根据该种调度策略完成所有工单,让外线工程师处理的工单获得的总分最多。 工程师调度可以顺序处理工单,当调度量为N,不会产生新的工单,每个工单处理修复耗时为1小时,请设计你的调度策略,完成业务目标。 不考虑外线工程师在小区之间行驶时。

二、输入描述

第一行为一个整数N,表示工单的数量。接下来N行,每行包括两个整数。第一个整数表示工单的SLA时间(小时),第二个数表示该工单的积分。

三、输出描述

输出一个整数表示可以获得的最大积分。

备注

  1. 工单数量 N ≤ 10^6
  2. SLA时间 ≤ 7 * 10^5
  3. 答案的最大积分不可以超过2147483647

四、测试用例

测试用例1:

1、输入

7
1 6
1 7
3 2
3 1
2 4
2 5
6 1

2、输出

15

3、说明

最多可获得15积分,其中一个调度结果完成工单顺序为2,6,3,1,7,5,4

测试用例2:

1、输入

5
2 10
1 5
2 7
1 8
3 6

2、输出

24

3、说明

工单排序后:(2,10), (1,8), (2,7), (3,6), (1,5)

分配结果:

工单(2,10)分配到时间槽2
工单(1,8)分配到时间槽1
工单(2,7)无法分配
工单(3,6)分配到时间槽3
工单(1,5)无法分配

总积分:10 + 8 + 6 = 24

五、解题思路

1、数据结构与算法

(1)贪心算法

选择高积分优先:通过将工单按照积分从高到低排序,优先分配高积分的工单,以确保总积分最大化。

选择最新可用时间槽:将每个工单分配到其SLA时间内的最新可用时间槽,尽可能为未来的工单留出更多空间。

(2)并查集(Union-Find)数据结构

高效查找和合并:并查集提供了接近常数时间复杂度的find和union操作,适用于大规模数据(N ≤ 10^6)的工单调度。

路径压缩优化:通过路径压缩技术,显著提高了并查集的操作效率,确保程序在处理大量工单时依然高效。

2、具体步骤:

  1. 输入读取:
    • 使用Scanner读取工单数量N。
    • 读取每个工单的deadline和score,同时记录最大deadline以初始化并查集。
  2. 工单排序:
    • 按照积分从高到低排序,确保优先处理高积分的工单。
  3. 工单调度:
    • 初始化并查集uf,大小为最大deadline。
    • 遍历每个工单,尝试将其分配到最新的可用时间槽。如果分配成功,累加积分,并更新并查集。
  4. 输出结果:
    • 输出所有分配成功的工单的总积分。

3、时间和空间复杂度

(1)时间复杂度:

排序工单的时间复杂度为O(N log N)。

并查集的find和union操作的平均时间复杂度接近O(1)。

因此,总体时间复杂度为O(N log N)。

(2)空间复杂度:

需要O(N)的空间存储工单信息。

并查集需要O(maxDeadline)的空间,最大为7 * 10^5。

六、Python算法源码

# 导入所需的模块
import sys

class UnionFind:
    """
    并查集(Union-Find)数据结构,用于高效地查找和合并可用的时间槽
    """
    def __init__(self, size):
        # 初始化父节点数组,每个节点的父节点为自己
        self.parent = list(range(size + 1))
    
    def find(self, x):
        """
        查找当前节点x的根节点,并进行路径压缩
        """
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 路径压缩
        return self.parent[x]
    
    def union(self, x, y):
        """
        合并两个节点x和y的集合
        """
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x != root_y:
            self.parent[root_x] = root_y  # 将x的根节点指向y的根节点

class Job:
    """
    工单类,包含SLA时间和积分
    """
    def __init__(self, deadline, score):
        self.deadline = deadline  # 工单的SLA时间
        self.score = score        # 工单的积分

def construct_longest_palindrome(s):
    pass  # 占位,不需要此函数

def main():
    # 读取所有输入
    input = sys.stdin.read().split()
    idx = 0
    N = int(input[idx])  # 工单数量
    idx += 1
    jobs = []
    max_deadline = 0  # 记录最大SLA时间
    
    # 读取每个工单的SLA时间和积分
    for _ in range(N):
        deadline = int(input[idx])
        score = int(input[idx + 1])
        jobs.append(Job(deadline, score))
        if deadline > max_deadline:
            max_deadline = deadline
        idx += 2
    
    # 按照积分从高到低排序,如果积分相同,则按SLA时间从小到大排序
    jobs.sort(key=lambda job: (-job.score, job.deadline))
    
    # 初始化并查集,大小为最大SLA时间
    uf = UnionFind(max_deadline)
    
    total_score = 0  # 使用long避免溢出(Python的int没有限制)
    
    # 遍历每个工单,尝试分配到最新的可用时间槽
    for job in jobs:
        # 查找当前工单的最新可用时间槽
        available_slot = uf.find(job.deadline)
        if available_slot > 0:
            total_score += job.score  # 累加积分
            # 将当前时间槽与前一个时间槽合并,表示该时间槽已被占用
            uf.union(available_slot, available_slot - 1)
    
    # 输出最大积分
    print(total_score)

if __name__ == "__main__":
    main()

七、JavaScript算法源码

// 最长回文串构造程序

/**
 * 工单类,包含SLA时间和积分
 */
class Job {
    constructor(deadline, score) {
        this.deadline = deadline; // 工单的SLA时间
        this.score = score;       // 工单的积分
    }
}

/**
 * 并查集(Union-Find)数据结构,用于高效地查找和合并可用的时间槽
 */
class UnionFind {
    constructor(size) {
        // 初始化父节点数组,每个节点的父节点为自己
        this.parent = Array.from({length: size + 1}, (_, i) => i);
    }

    /**
     * 查找当前节点x的根节点,并进行路径压缩
     * @param {number} x 当前节点
     * @returns {number} 根节点
     */
    find(x) {
        if (this.parent[x] !== x) {
            this.parent[x] = this.find(this.parent[x]); // 路径压缩
        }
        return this.parent[x];
    }

    /**
     * 合并两个节点x和y的集合
     * @param {number} x 节点x
     * @param {number} y 节点y
     */
    union(x, y) {
        let rootX = this.find(x);
        let rootY = this.find(y);
        if (rootX !== rootY) {
            this.parent[rootX] = rootY; // 将x的根节点指向y的根节点
        }
    }
}

/**
 * 主函数,读取输入并计算最大积分
 */
function main() {
    const readline = require('readline');

    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    let input = [];

    rl.on('line', function(line) {
        input = line.trim().split(/\s+/).map(Number);
    }).on('close', function() {
        let idx = 0;
        const N = input[idx++]; // 工单数量
        let jobs = [];
        let max_deadline = 0; // 记录最大SLA时间

        // 读取每个工单的SLA时间和积分
        for (let i = 0; i < N; i++) {
            let deadline = input[idx++];
            let score = input[idx++];
            jobs.push(new Job(deadline, score));
            if (deadline > max_deadline) {
                max_deadline = deadline;
            }
        }

        // 按照积分从高到低排序,如果积分相同,则按SLA时间从小到大排序
        jobs.sort((a, b) => {
            if (b.score !== a.score) {
                return b.score - a.score; // 降序排列积分
            } else {
                return a.deadline - b.deadline; // 升序排列SLA时间
            }
        });

        // 初始化并查集,大小为最大SLA时间
        let uf = new UnionFind(max_deadline);

        let total_score = 0; // 累加积分

        // 遍历每个工单,尝试分配到最新的可用时间槽
        for (let job of jobs) {
            // 查找当前工单的最新可用时间槽
            let available_slot = uf.find(job.deadline);
            if (available_slot > 0) {
                total_score += job.score; // 累加积分
                // 将当前时间槽与前一个时间槽合并,表示该时间槽已被占用
                uf.union(available_slot, available_slot - 1);
            }
        }

        // 输出最大积分
        console.log(total_score);
        process.exit(0);
    });
}

main();

八、C算法源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 工单类,包含SLA时间和积分
typedef struct {
    int deadline; // 工单的SLA时间
    int score;    // 工单的积分
} Job;

// 并查集(Union-Find)数据结构,用于高效地查找和合并可用的时间槽
typedef struct {
    int *parent;
} UnionFind;

/**
 * 初始化并查集
 * @param size 并查集的大小
 * @return 初始化后的并查集
 */
UnionFind* init_union_find(int size) {
    UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
    uf->parent = (int*)malloc((size + 1) * sizeof(int));
    for(int i = 0; i <= size; i++) {
        uf->parent[i] = i; // 初始化每个节点的父节点为自己
    }
    return uf;
}

/**
 * 查找当前节点x的根节点,并进行路径压缩
 * @param uf 并查集
 * @param x 当前节点
 * @return 根节点
 */
int find(UnionFind* uf, int x) {
    if(uf->parent[x] != x) {
        uf->parent[x] = find(uf, uf->parent[x]); // 路径压缩
    }
    return uf->parent[x];
}

/**
 * 合并两个节点x和y的集合
 * @param uf 并查集
 * @param x 节点x
 * @param y 节点y
 */
void union_sets(UnionFind* uf, int x, int y) {
    int root_x = find(uf, x);
    int root_y = find(uf, y);
    if(root_x != root_y) {
        uf->parent[root_x] = root_y; // 将x的根节点指向y的根节点
    }
}

/**
 * 比较函数,用于qsort,按照积分从高到低排序,如果积分相同,则按SLA时间从小到大排序
 */
int compare(const void* a, const void* b) {
    Job* jobA = (Job*)a;
    Job* jobB = (Job*)b;
    if(jobB->score != jobA->score) {
        return jobB->score - jobA->score; // 降序排列积分
    } else {
        return jobA->deadline - jobB->deadline; // 升序排列SLA时间
    }
}

int main() {
    int N;
    // 读取工单数量
    if(scanf("%d", &N) != 1) {
        return 0;
    }
    Job* jobs = (Job*)malloc(N * sizeof(Job));
    int max_deadline = 0; // 记录最大SLA时间
    
    // 读取每个工单的SLA时间和积分
    for(int i = 0; i < N; i++) {
        if(scanf("%d %d", &jobs[i].deadline, &jobs[i].score) != 2) {
            return 0;
        }
        if(jobs[i].deadline > max_deadline) {
            max_deadline = jobs[i].deadline;
        }
    }
    
    // 按照积分从高到低排序
    qsort(jobs, N, sizeof(Job), compare);
    
    // 初始化并查集,大小为最大SLA时间
    UnionFind* uf = init_union_find(max_deadline);
    
    long long total_score = 0; // 使用long long避免溢出
    
    // 遍历每个工单,尝试分配到最新的可用时间槽
    for(int i = 0; i < N; i++) {
        int available_slot = find(uf, jobs[i].deadline);
        if(available_slot > 0) {
            total_score += jobs[i].score; // 累加积分
            // 将当前时间槽与前一个时间槽合并,表示该时间槽已被占用
            union_sets(uf, available_slot, available_slot - 1);
        }
    }
    
    // 输出最大积分
    printf("%lld\n", total_score);
    
    // 释放内存
    free(jobs);
    free(uf->parent);
    free(uf);
    
    return 0;
}

九、C++算法源码

#include <bits/stdc++.h>
using namespace std;

/**
 * 工单类,包含SLA时间和积分
 */
struct Job {
    int deadline; // 工单的SLA时间(小时)
    int score;    // 工单的积分
};

/**
 * 并查集(Union-Find)数据结构,用于高效地查找和合并可用的时间槽
 */
struct UnionFind {
    vector<int> parent; // 父节点数组

    /**
     * 构造函数,初始化并查集
     * @param size 并查集的大小(最大SLA时间)
     */
    UnionFind(int size) {
        parent.resize(size + 1);
        for(int i = 0; i <= size; i++) {
            parent[i] = i; // 初始化每个节点的父节点为自己
        }
    }

    /**
     * 查找当前节点x的根节点,并进行路径压缩
     * @param x 当前节点
     * @return x的根节点
     */
    int find_set(int x) {
        if(parent[x] != x) {
            parent[x] = find_set(parent[x]); // 路径压缩,递归查找并更新父节点
        }
        return parent[x];
    }

    /**
     * 合并两个节点x和y的集合
     * @param x 节点x
     * @param y 节点y
     */
    void union_set(int x, int y) {
        int root_x = find_set(x); // 查找x的根节点
        int root_y = find_set(y); // 查找y的根节点
        if(root_x != root_y) {
            parent[root_x] = root_y; // 将x的根节点指向y的根节点,实现合并
        }
    }
};

/**
 * 比较函数,用于排序工单
 * 按照积分从高到低排序,如果积分相同,则按SLA时间从小到大排序
 * @param a 第一个工单
 * @param b 第二个工单
 * @return true 如果a应该排在b前面,否则false
 */
bool compare_jobs(const Job &a, const Job &b) {
    if(a.score != b.score) {
        return a.score > b.score; // 积分高的排在前面
    } else {
        return a.deadline < b.deadline; // SLA时间小的排在前面
    }
}

int main(){
    ios::sync_with_stdio(false); // 关闭C++的同步机制,加快输入输出速度
    cin.tie(0); // 解除cin和cout的绑定,进一步加快速度

    int N; // 工单数量
    cin >> N; // 读取工单数量
    vector<Job> jobs(N); // 创建一个大小为N的工单数组
    int max_deadline = 0; // 记录最大SLA时间

    // 读取每个工单的SLA时间和积分
    for(int i = 0; i < N; i++) {
        cin >> jobs[i].deadline >> jobs[i].score; // 读取SLA时间和积分
        if(jobs[i].deadline > max_deadline) {
            max_deadline = jobs[i].deadline; // 更新最大SLA时间
        }
    }

    // 按照积分从高到低排序,如果积分相同,则按SLA时间从小到大排序
    sort(jobs.begin(), jobs.end(), compare_jobs);

    // 初始化并查集,大小为最大SLA时间
    UnionFind uf(max_deadline);

    long long total_score = 0; // 使用long long避免溢出,存储总积分

    // 遍历每个工单,尝试分配到最新的可用时间槽
    for(auto &job : jobs){
        // 查找当前工单的最新可用时间槽
        int available_slot = uf.find_set(job.deadline);
        if(available_slot > 0){
            total_score += job.score; // 累加积分
            // 将当前时间槽与前一个时间槽合并,表示该时间槽已被占用
            uf.union_set(available_slot, available_slot - 1);
        }
    }

    // 输出最大积分
    cout << total_score << "\n";

    return 0;
}


🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2024 E卷 200分)

🏆本文收录于,华为OD机试真题(Python/JS/C/C++)

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值