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<50002
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. 贪心算法
由于题目要求是 优先输出的是“字典序”最小的有效调度,所以贪心思想也是比较容易想到的,即:每次都从头往后找,找到第一个满足视频流调度条件的,就进行记录,然后重新从头往后找,直到所有视频流都记录为止(即所有视频流都已发送)。
算法思路
- 对输入的数据进行记录,并用变量 currentByte 记录当前视频流总字节数,用变量 currentClock 记录当前消耗的总时间单位数。
- 然后双循环,外层循环 i 记录当前已经记录的视频流个数,即要输出的数组的一个个元素。
- 内层循环为主要算法,j 从头往后遍历,如果该序号没被记录过(数组 v[i] 没被标记过),且满足视频流调度条件:加上该视频,字节也在链路带宽限制范围内。
- 找到视频后,就进行记录,然后继续循环,直到所有视频都记录为止。
更多注释可查看下方的完整代码中,有助于理解。
代码如下
#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,所以该算法会超时,但也算是提供了一种新思路。
算法思路
- 函数 f 用于搜索,参数有三个,一个是记录当前记录的视频个数 cur,一个是记录当前记录的视频的总字节数,一个是记录当前记录所消耗的总时钟周期。
- 递归终止条件为:记录的视频个数与 n 相等
- 每次 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;
}