算法设计与分析 SCAU17104 视频流有效调度

17104 视频流有效调度

时间限制:1000MS 代码长度限制:10KB
提交次数:25 通过次数:9

题型: 编程题 语言: G++;GCC;VC;JAVA

在这里插入图片描述


Description

现在n个视频流要在一条通信链路上一个接一个的传送。视频流i由bi位组成,这些位需要一个常数速率,
在ti秒内被发送。你不可能同时发送两个视频流,因此需要确定一个关于视频流的调度。
无论你选择哪一个次序,在一个视频流的结束与下一个视频流开始之间不能有任何延迟。假如你调度在时
刻0开始,无论采用什么次序,都将结束于sum{ ti | i from 1 to n}这个时刻。我们假设所有的bi和ti都
是正整数。

现在引入一个链路限制参数r。由于你仅仅是一个用户,这个链路不想让你占用太多的带宽,因此规定这个限制
条件:对每个自然数t>0,你在从0到t的时间区间内发送的总位数不能超过rt。这个规定是对开始于0的时间区间
的,而不是开始于任何别的时间区间的。满足这个限制的调度才是有效的。

给定n个视频流,每个视频流位数bi,持续时间ti,以及链路限制参数r。比如,n=3个视频流,具有:
(b1,t1)=(2000,1)
(b2,t2)=(6000,2)
(b3,t3)=(2000,1)
链路限制参数r=5000。

则按照1,2,3的次序运行这个视频流的调度室有效的,因为:
t=1:第1个视频流全部被发送,且2000<50001
t=2:第2个视频流一半被发送,且2000+3000<5000
2
t=3与t=4,类似的结论也成立。

现在给出一个多项式运行时间的算法,确定是否存在一个有效调度?并输出这个有效调度。


输入格式

第一行:n r (n表示视频流个数,r表示链路限制参数,中间空格,n<10000,r>0)
接下来n行,都是这样组合:bi ti 1<=i<=n


输出格式

n个视频流有效的调度顺序(这个调度顺序不一定唯一,我们优先输出字典序排前的那一种调度)。
若不存在有效的调度顺序,输出“no”(无大写无标点)。


输入样例

3 5000
2000 1
6000 2
2000 1


输出样例

1 2 3


解题思路

1. 贪心算法

由于题目要求是 优先输出的是“字典序”最小的有效调度,所以贪心思想也是比较容易想到的,即:每次都从头往后找,找到第一个满足视频流调度条件的,就进行记录,然后重新从头往后找,直到所有视频流都记录为止(即所有视频流都已发送)。


算法思路
  1. 对输入的数据进行记录,并用变量 currentByte 记录当前视频流总字节数,用变量 currentClock 记录当前消耗的总时间单位数。
  2. 然后双循环,外层循环 i 记录当前已经记录的视频流个数,即要输出的数组的一个个元素。
  3. 内层循环为主要算法,j 从头往后遍历,如果该序号没被记录过(数组 v[i] 没被标记过),且满足视频流调度条件:加上该视频,字节也在链路带宽限制范围内。
  4. 找到视频后,就进行记录,然后继续循环,直到所有视频都记录为止。



更多注释可查看下方的完整代码中,有助于理解。

代码如下
#include <vector>
#include <utility>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int byte[10001];
int Time[10001];
vector<int> num; // 存各视频流编号,即 cur,要输出的
int v[10001]; // v[i] 用于标记数字是否被选中

int main()
{
    int i, j, n, r;
    cin >> n >> r;

    for(i = 1; i <= n; i++) {
        cin >> byte[i] >> Time[i];
    }

    int currentByte = 0; // 当前视频流总数
    int currentClock = 0; // 当前消耗的时间单位数

    // 当前要输出的数组记录了几位编号了
    for(i = 1; i <= n; i++) {
        int flag = 0; // 用于表示本轮从头往后找,有无找到满足视频流调度的序号

        for(j = 1; j <= n; j++) {
            // 该视频流没被选过且加上该视频流后,字节还在范围内,即找到满足条件的视频流了
            if(!v[j] && currentByte + byte[j] <= (currentClock + Time[j]) * r) {
                v[j] = 1;
                flag = j;
                break;
            }
        }

        // 说明本轮从头往后找没找到满足视频流调度的序号,由于还没发完全部视频流,所以不满足
        if(flag == 0) {
            cout << "no" << endl;
            return 0;
        }

        // 若找到视频流,进行相关的操作
        currentByte += byte[j];
        currentClock += Time[j];
        num.push_back(j);
    }

    for(i = 0; i < n; i++) {
        cout << num[i] << " ";
    }

    return 0;
}


2. 搜索 + 回溯 + 剪枝

此题我们的思想类似于全排列,也就是将所有序号按字典序从小到大排列出来,当找到第一个满足条件的序列,就停止算法。

但由于 n < 10000,所以该算法会超时,但也算是提供了一种新思路。


算法思路
  1. 函数 f 用于搜索,参数有三个,一个是记录当前记录的视频个数 cur,一个是记录当前记录的视频的总字节数,一个是记录当前记录所消耗的总时钟周期。
  2. 递归终止条件为:记录的视频个数与 n 相等
  3. 每次 for 循环进行全排列时,条件不仅是没被记录过(v[i] == 0),还有 加上该视频,字节也在链路带宽限制范围内(sum + byte[i] <= r * (clock + Time[i]))

为什么要回溯呢?
因为我记录序号都共用一个 num 数组,在同一个循环内,如果你比如在索引为2时,想输出序号为3的情况,又想输出序号为4的情况,那就得想之前的给 “撤销” 了,再继续,同时标志数组也记得撤销哈



更多注释可查看下方的完整代码中,有助于理解。

代码如下
#include <iostream>
#include <vector>
#include <string.h>


using namespace std;

int byte[10001];
int Time[10001];
int n, r;
//int sum = 0; // 前面的视频流总值
vector<int> num; // 存各视频流编号,即 cur,要输出的
int v[10001]; // v[i] 用于标记数字是否被选中
int flag = 0; // 如果 flag 为1,说明找到序列了,停止算法

void f(int cur, int sum, int clock) {
    if(flag == 1)
        return;

    // 检查到最后一个看看满不满足调度了,如果还满足就不用递归了,直接输出并暂停算法
    if(cur == n + 1) {
        // 调度范围内
        for(int i = 0; i < num.size(); i++) {
            cout << num[i] << " ";
        }

        //cout << endl;
        flag = 1;
        return;
    }

    for(int i = 1; i <= n; i++) {
        if(!v[i]) {
            // 看看若加入当前编号的流,满不满足调度,如果还满足就继续递归了,否则直接剪枝
            //cout << "cur=" << cur << " sum=" << sum << " byte=" << byte[cur] << " Time=" << Time[cur] << " clock=" << clock << endl;
            if(sum + byte[i] <= r * (clock + Time[i])) {
                v[i] = 1;
                num.push_back(i);
                f(cur + 1, sum + byte[i], clock + Time[i]);
                num.pop_back();
                v[i] = 0;
            }
        }

    }
}

int main()
{
    memset(v, 0, sizeof(v));
    // 搜索+回溯+剪枝
    cin >> n >> r;

    for(int i = 1; i <= n; i++) {
        cin >> byte[i] >> Time[i];
    }

    f(1, 0, 0);

    // 如果执行完上面的算法,flag 还是0,即没有找到合适序列,那就输出 no
    if(flag == 0) {
        cout << "no" << endl;
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现平衡二叉树的各种算法(如AVL树、红黑树等)可以应用在SCAU(South China Agricultural University,华南农业大学)的数据结构与算法课程中。这些算法的目标是确保二叉树的左右子树高度差不超过1,从而使树的高度保持相对较小的水平,提高插入、删除和查找等操作的效率。 一种常见的平衡二叉树算法是AVL树。实现AVL树的关键是通过旋转操作来保持树的平衡。在SCAU,可以使用多种编程语言(如C++、Java等)实现AVL树算法。插入新节点时,首先按照二叉查找树的方式找到合适的插入置,然后通过不同的旋转操作调整树的平衡。具体的实现包括左旋、右旋、左右旋和右左旋等操作。 另一种常见的平衡二叉树算法是红黑树。SCAU可以使用编程语言(如C++、Java等)实现红黑树算法。红黑树通过使用辅助信息(即节点的颜色)来维持树的平衡。红黑树的插入操作包括节点的颜色变换和旋转操作。具体实现包括左旋、右旋、颜色变换等操作。 无论是AVL树还是红黑树,它们的实现都需要处理平衡调整,在插入或删除节点时通过旋转和颜色变换等操作来保持树的平衡。实现这些算法需要对平衡树的定义和性质有深入的理解,并具备编程技巧和数据结构基础。SCAU的学生在学习数据结构与算法课程时,可以通过理论学习和实践编程来掌握实现平衡二叉树的各种算法。除了课程的学习,还可以通过参考相关的教材、博客、论文等来加深对平衡二叉树算法实现的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值