返回目录

题目描述

项目组共有N个开发人员,项目经理接到了M个独立的需求,每个需求的工作量不同,且每个需求只能由一个开发人员独立完成,不能多人合作。假定各个需求直接无任何先后依赖关系,请设计算法帮助项目经理进行工作安排,使整个项目能用最少的时间交付。

输入描述

第一行输入为M个需求的工作量,单位为天,用逗号隔开。

例如:X1 X2 X3 … Xm 。表示共有M个需求,每个需求的工作量分别为X1天,X2天…Xm天。

其中0<M<30;0<Xm<200

第二行输入为项目组人员数量N

输出描述

最快完成所有工作的天数

输入6 2 7 7 9 3 2 1 3 11 4
2
输出28
说明共有两位员工,其中一位分配需求 6 2 7 7 3 2 1共需要28天完成,
另一位分配需求 9 3 11 4 共需要27天完成,故完成所有工作至少
需要28天。

给定一系列任务的工作量和一定数量的工人,计算完成所有任务所需的最少天数,使得每个工人分配到的任务总工作量不超过这个天数。这是一个典型的搜索问题,可以通过回溯法和二分查找结合来解决。

  1. 排序和反转任务数组

    • 使用 Arrays.sort(tasks)对任务数组进行升序排序,然后通过一个循环将数组反转,使其成为降序。这样做是为了优先分配工作量大的任务,从而更高效地利用工人的工作时间。
  2. 二分查找

    • 为了找到完成所有任务所需的最少天数,使用二分查找确定这个最小值。设置两个指针 lr,分别表示可能的最短时间的下界和上界。l初始化为数组中的最大值(即最大的单个任务工作量),r初始化为所有任务工作量的总和。
    • l小于 r的条件下进行循环,计算中间值 mid,并使用 canFinish函数检查是否可以在 mid天内完成所有任务。
    • 如果可以完成,则将上界 r设置为 mid,否则将下界 l设置为 mid + 1
    • lr相遇时,l即为所求的最少天数。
  3. 回溯法

    • canFinish函数使用回溯法来检查在给定的时间限制 limit内是否可以完成所有任务。
    • 创建一个长度为工人数量 k的数组 workers,用于记录每个工人的当前工作量。
    • 使用 backtrack函数递归地尝试为每个任务分配工人,直到所有任务都被分配或者无法在时间限制内完成分配。
    • backtrack函数中,如果当前工人可以在时间限制内完成当前任务,则将任务分配给他,并递归地尝试分配下一个任务。
    • 如果分配成功,则返回 true;如果当前路径无法成功分配所有任务,则回溯到上一个状态,尝试其他可能的分配方案。
    • 如果所有方案都无法成功,则返回 false

Python算法源码

def can_finish(tasks, workers, index, limit):
    return backtrack(tasks, workers, index, limit)

def backtrack(tasks, workers, index, limit):
    if index >= len(tasks):
        return True
  
    current = tasks[index]
  
    for i in range(len(workers)):
        if workers[i] + current <= limit:
            workers[i] += current
            if backtrack(tasks, workers, index + 1, limit):
                return True
            workers[i] -= current
      
        if workers[i] == 0 or workers[i] + current == limit:
            break
  
    return False

def minimum_time_required(tasks, k):
    tasks.sort(reverse=True)
    l, r = tasks[0], sum(tasks)
  
    while l < r:
        mid = (l + r) // 2
        if can_finish(tasks, [0] * k, 0, mid):
            r = mid
        else:
            l = mid + 1
  
    return l

if __name__ == "__main__":
    # 读取输入
    workloads = list(map(int, input().split()))
    # 读取项目组人员数量
    N = int(input())
    # 输出最快完成所有工作的天数
    print(minimum_time_required(workloads, N))

C语言算法源码

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

#define MAX_TASKS 30 // 定义最大任务数量的常量,用于设置任务数组的最大长度

// 用于qsort函数的比较函数,实现降序排序
int compare(const void *x, const void *y) {
    // 将void指针转换为int指针,并解引用获取值进行比较
    return (*(int *)y - *(int *)x);
}

// 回溯法分配任务
int backtrack(int *tasks, int *workers, int index, int limit, int k, int taskSize) {
    // 检查是否所有任务都已分配
    if (index >= taskSize) {
        return 1; // 如果是,返回1表示成功
    }

    // 获取当前要分配的任务
    int current = tasks[index];
    // 遍历所有员工
    int i = 0;
    while (i < k) {
        // 检查当前员工是否可以在时间限制内完成这个任务
        if (workers[i] + current <= limit) {
            // 如果可以,分配任务并递归尝试分配下一个任务
            workers[i] += current;
            if (backtrack(tasks, workers, index + 1, limit, k, taskSize)) {
                return 1;
            }
            // 如果不成功,回溯,即撤销这次任务分配
            workers[i] -= current;
        }

        // 如果当前员工没有任务或者加上当前任务刚好达到时间限制,则不需要尝试其他员工
        if (workers[i] == 0 || workers[i] + current == limit) {
            break;
        }
        i++;
    }

    // 如果无法分配当前任务,返回0表示失败
    return 0;
}

// 检查是否能在指定时间内完成所有任务
int canFinish(int *tasks, int k, int limit, int taskSize) {
    // 初始化一个记录员工当前任务量的数组
    int workers[MAX_TASKS] = {0};
    // 调用回溯法尝试分配任务
    return backtrack(tasks, workers, 0, limit, k, taskSize);
}

// 计算完成所有任务的最短时间
int minimumTimeRequired(int *tasks, int k, int taskSize) {
    // 先对任务进行降序排序
    qsort(tasks, taskSize, sizeof(int), compare);

    // 二分查找的左右边界,左边界为最大单个任务时间,右边界为所有任务时间总和
    int left = tasks[0], right = 0;
    for (int i = 0; i < taskSize; i++) {
        right += tasks[i];
    }

    // 二分查找最短完成时间
    while (left < right) {
        int mid = left + (right - left) / 2;
        // 检查是否能在mid时间内完成所有任务
        if (canFinish(tasks, k, mid, taskSize)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }

    // 返回最短完成时间
    return left;
}


int irrelevantVariable = 0;


struct Person {
    char name[30];
    int age;
};


void printPersonInfo(struct Person person) {
    printf("Name: %s\n", person.name);
    printf("Age: %d\n", person.age);
}

int main() {
    // 存储任务的数组和任务数量
    int tasks[MAX_TASKS], taskSize = 0;
    // 读取一行输入作为任务工作量
    char input[200];
    fgets(input, 200, stdin);
    // 使用strtok分割字符串,将分割后的数字转换为int存入任务数组
    char *token = strtok(input, " ");
    while (token != NULL) {
        tasks[taskSize++] = atoi(token);
        token = strtok(NULL, " ");
    }

    // 读取员工数量
    int employees;
    scanf("%d", &employees);

    // 计算并输出完成所有任务的最短时间
    printf("%d\n", minimumTimeRequired(tasks, employees, taskSize));

    // 结构体变量和函数调用
    struct Person person;
    strcpy(person.name, "John Doe");
    person.age = 30;
    printPersonInfo(person);

    return 0;
}

Java算法源码

import java.util.*;

public class Main {
    // 回溯法
    public static boolean backtrack(int[] tasks, int[] workers, int index, int limit) {
        // 如果所有任务都已分配,则返回true
        if (index >= tasks.length) {
            return true;
        }

        // 获取当前任务的工作量
        int current = tasks[index];
        // 尝试将当前任务分配给每个员工
        for (int i = 0; i < workers.length; i++) {
            // 如果当前员工可以在时间限制内完成这项任务
            if (workers[i] + current <= limit) {
                // 分配任务给当前员工
                workers[i] += current;
                // 继续尝试分配下一个任务
                if (backtrack(tasks, workers, index + 1, limit)) {
                    return true;
                }
                // 回溯,取消当前的任务分配
                workers[i] -= current;
            }

            // 如果当前员工没有任务或者加上当前任务刚好达到时间限制,则不需要尝试其他员工
            if (workers[i] == 0 || workers[i] + current == limit) {
                break;
            }
        }

        // 如果无法分配当前任务,则返回false
        return false;
    }

    // 检查是否可以在给定的时间限制内完成所有任务
    public static boolean canFinish(int[] tasks, int k, int limit) {
        // 创建一个数组来记录每个员工的工作量
        int[] workers = new int[k];
        // 使用回溯法检查是否可以完成
        return backtrack(tasks, workers, 0, limit);
    }

    // 计算完成所有任务所需的最少天数
    public static int minimumTimeRequired(int[] tasks, int k) {
        // 将任务按工作量降序排序
        Arrays.sort(tasks);
        int[] reversedTasks = new int[tasks.length];
        for (int i = 0; i < tasks.length; i++) {
            reversedTasks[i] = tasks[tasks.length - 1 - i];
        }

        // 使用二分查找确定完成所有任务的最短时间
        int l = reversedTasks[0], r = Arrays.stream(reversedTasks).sum();
        while (l < r) {
            int mid = (l + r) / 2;
            // 检查当前时间限制是否足够完成所有任务
            if (canFinish(reversedTasks, k, mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }

        // 返回最短完成时间
        return l;
    }

    public static void main(String[] args) {
        // 使用Scanner读取输入
        Scanner scanner = new Scanner(System.in);
        String[] input = scanner.nextLine().split(" ");
        int[] tasks = new int[input.length];
        for (int i = 0; i < input.length; i++) {
            tasks[i] = Integer.parseInt(input[i]);
        }
        int N = scanner.nextInt();

        // 输出最快完成所有工作的天数
        System.out.println(minimumTimeRequired(tasks, N));
    }
}
最后修改:2024 年 04 月 01 日
如果觉得我的文章对你有用,请随意赞赏