题目背景
西西艾弗岛上兴建了一批数据中心,建设了云计算资源平台。小 C 是主管西西艾弗云开发的工程师。西西艾弗云中有大量的计算节点,每个计算节点都有唯一编号。
西西艾弗云分为多个可用区,每个计算节点位于一个特定的可用区。一个可用区中可以有多个计算节点。
西西艾弗云中运行的计算任务分为不同的应用,每个计算任务都有一个应用与之对应,一个应用中可能包括多个计算任务。
每个计算任务由一个特定的计算节点执行,下文中计算任务“运行在某可用区上”意即“运行在某可用区的计算节点上”。
不同的计算任务对运行的计算节点的要求不尽相同。有的计算任务需要在指定可用区上运行,有的计算任务要和其它应用的计算任务在同一个可用区上运行,
还有的希望不要和某个应用的计算任务在同一个计算节点上运行。
对于一个计算任务,执行它的计算节点一旦选定便不再更改;在选定计算节点后,该任务对计算节点的要求就不再被考虑,
即使新安排的计算任务使得此前已有的计算任务的要求被违反,也是合法的。
下图示意性地说明了可用区、计算节点、计算任务之间的关系,同时也说明了应用和计算任务的对应关系。
一开始,小 C 使用了电子表格程序来统计计算任务的分配情况。随着云上的计算节点和计算任务的不断增多,小 C 被这些奇怪的要求搞得焦头烂额,有的时候还弄错了安排,让客户很不满意。
小 C 找到你,希望你能为他提供一个程序,能够输入要运行的计算任务和对节点的要求,结合西西艾弗云的现有计算节点信息,计算出计算任务应该被安排在哪个计算节点上。
问题描述
计算任务对计算节点的要求十分复杂而且又不好描述,你对小 C 表示写程序这件事很为难。于是,小 C 进行了调研,
将这些需求进行了归纳整理,形成了下面这三种标准需求。在提出需求时,必须从这三种标准需求中选取若干种,每种需求只能有一条。选取多种需求意味着要同时满足这些需求。
-
计算节点亲和性
计算任务必须在指定可用区上运行。
-
计算任务亲和性
计算任务必须和指定应用的计算任务在同一可用区上运行。
该要求对计算任务可以运行的可用区添加了限制。不考虑该任务本身,一个可用区若运行有指定应用的任务,则满足要求。
-
计算任务反亲和性
计算任务不能和指定应用的计算任务在同一个计算节点上运行。
该要求对计算任务可以运行的计算节点添加了限制。不考虑该任务本身,一个计算节点若运行有指定应用的任务,则不满足要求。
当要执行的计算任务多起来,计算任务反亲和性的要求可能很难满足。因此在添加计算任务反亲和性要求时,还要指定这个要求是“必须满足”还是“尽量满足”。
小 C 要求你按照如下方法来分配计算节点:按照计算任务的启动顺序,根据要求,依次为每个计算任务选择计算节点。一旦选择了一个计算节点,就固定下来不再变动,
并且在此后的选择中,不再考虑这个计算任务的要求。对每个计算任务,选择计算节点的方法是:
-
过滤阶段
在这个阶段,先根据计算任务的要求,过滤出所有满足要求的计算节点。如果不存在这样的计算节点,并且指定了计算任务反亲和性要求,并且计算任务反亲和性要求是尽量满足的,
那么去掉计算任务反亲和性要求,再过滤一次。如果还不存在,就认为该计算任务的要求无法满足,该计算任务无法分配。 -
排序阶段
在这个阶段,将过滤后的计算节点按照这个方法排序:
- 选择此时运行计算任务数量最少的计算节点;
- 选择编号最小的计算节点。
输入格式
输入的第一行包含两个由空格分隔的正整数 n 和 m,分别表示计算节点的数目和可用区的数目。计算节点从 1 到 n 编号,可用区从 1 到 m 编号;
输入的第二行包含 n 个由空格分隔的正整数 l1,l2,…li,…,ln,表示编号为 i 的计算节点位于编号为 li 的可用区。其中,0<li≤m;
输入的第三行包含一个正整数 g,表示计算任务的组数;
接下来的 g 行,每行包含六个由空格分隔的整数 fi、ai、nai、pai、paai、paari,表示依次启动的一组计算任务的信息,其中:
- fi:表示要接连启动 fi 个所属应用和要求相同的计算任务,其中 fi>0;
- ai:表示这 fi 个计算任务所属应用的编号,其中 0<ai≤Amax(Amax 代表最大应用编号);
- nai:表示计算节点亲和性要求,其中 0≤nai≤m。当 nai=0 时,表示没有计算节点亲和性要求;否则表示要运行在编号为 nai 的可用区内的计算节点上;
- pai:表示计算任务亲和性要求,其中 0≤pai≤Amax。当 pai=0 时,表示没有计算任务亲和性要求;否则表示必须和编号为 pai 的应用的计算任务在同一个可用区运行;
- paai:表示计算任务反亲和性要求,其中 0≤paai≤Amax。当 paai=0 时,表示没有计算任务反亲和性要求;否则表示不能和编号为 paai 的应用的计算任务在同一个计算节点上运行;
- paari:表示计算任务亲和性要求是必须满足还是尽量满足,当 paai=0 时,paari 也一定为 0;否则 paari=1 表示“必须满足”,paari=0 表示“尽量满足”。
计算任务按组输入实际上是一种简化的记法,启动一组 (fi,ai,nai,pai,paai,paari) 和连续启动 fi 组 (1,ai,nai,pai,paai,paari) 并无不同。
输出格式
输出 g 行,每行有 fi 个整数,由空格分隔,分别表示每个计算任务被分配的计算节点的情况。若该计算任务没有被分配,则输出 0;否则输出被分配的计算节点的编号。
样例输入
10 4
1 1 1 1 1 2 2 2 2 2
6
2 1 4 1 2 1
6 1 1 0 1 1
1 2 2 0 0 0
6 1 2 0 2 1
5 2 2 0 1 0
11 3 0 1 3 0
样例输出
0 0
1 2 3 4 5 0
6
7 8 9 10 7 8
6 6 6 6 6
1 2 3 4 5 9 10 7 8 6 1
样例解释
本输入中声明了十个计算节点,前五个位于可用区 1,后五个位于可用区 2。可用区 3 和 4 不包含任何计算节点。
对于第一组计算任务,由于它们声明了计算节点亲和性要求,但要求的可用区编号是 4,该可用区不包含计算节点,因此都不能满足。
对于第二组计算任务,要在可用区 1 中启动 6 份应用 1 的任务,并且要求了计算任务反亲和性。因此,前五份任务分别被安排在前五个节点上。对于第六份任务,由于它必须运行于可用区 1,所以能够安排的范围仅限于前五个节点。但是它还指定了强制的计算任务反亲和性,前五个节点上已经启动了属于应用 1 的计算任务,因此没有能够运行它的节点。
对于第三组计算任务,要在可用区 2 中启动 1 份应用 2 的任务,直接将其分配给节点 6。
对于第四组计算任务,要在可用区 2 中启动 6 份应用 1 的任务,并且要求了计算任务反亲和性,不能和应用 2 的计算任务分配在同一个节点上。因此,节点 6 不能用于分配,这六份任务只能分配在节点 7~10 上。按照题意,选取运行任务数最少的和编号最小的,因此依次分配 7、8、9、10、7、8。
对于第五组计算任务,要在可用区 2 中启动 5 份应用 2 的任务,并且要求了尽量满足的计算任务反亲和性,不能和应用 1 的计算任务分配在同一个节点上。此时,可用区 2 中的节点 6 上没有应用 1 的计算任务,因此这 5 份计算任务都会被分配到这个节点上。
对于第六组计算任务,要启动 11 份应用 3 的任务,并且要求了尽量满足的计算任务反亲和性,不能和应用 3 的其它计算任务分配在同一个节点上,同时要求和应用 1 的计算任务位于同一个可用区。应用 1 位于两个可用区,因此全部 10 个节点都可以用于分配。对于前 10 份任务,按照题意,依次选取运行的任务数最少且编号最小的节点进行分配。对于第 11 份任务,由于所有的节点上都运行有应用 3 的任务,因此没有节点符合它的反亲和性要求。又因为反亲和性要求是尽量满足的,因此可以忽略这一要求,将它安排在节点 1 上。
子任务
本题包含 20 个测试用例,每个 5 分。
全部测试数据保证 0<m≤n≤1000,0<g≤∑i=1gfi≤2000。
部分测试点的特殊性质详见下表,比如测试点 1、2 中最大应用编号仅为 10 且不包含任何需求。
测试点 | 最大应用编号 | 计算节点亲和性 | 计算任务亲和性 | 计算任务反亲和性 |
---|---|---|---|---|
1, 2 | Amax=10 | 无要求(nai=0) | 无要求(pai=0) | 无要求(paai=0) |
3, 4 | Amax=109 | 无要求(nai=0) | 无要求(pai=0) | 无要求(paai=0) |
5, 6, 7 | Amax=10 | nai≥0 | 无要求(pai=0) | 无要求(paai=0) |
8, 9, 10 | Amax=109 | nai≥0 | 无要求(pai=0) | 无要求(paai=0) |
11, 12, 13 | Amax=10 | nai≥0 | 无要求(pai=0) | paai≥0 |
14, 15, 16 | Amax=109 | nai≥0 | 无要求(pai=0) | paai≥0 |
17, 18 | Amax=10 | nai≥0 | pai≥0 | paai≥0 |
19, 20 | Amax=109 | nai≥0 | pai≥0 | paai≥0"] |
AC代码:
#include<iostream>
#include<set>
#include<vector>
#include<unordered_map>
#include<algorithm>
using namespace std;
struct node//节点
{
int id;//第几个计算节点
int block;//属于哪个区
int num;//该节点上有几个任务
set<int> task;//该节点上有哪些任务
};
vector<int> v;//v是节点整型
unordered_map<int, node> mp;//节点id到节点结构体的映射
int func1(int n, int flag, vector<int> &temp)//计算节点亲和性
{
int left=n;
vector<int>::iterator i=temp.begin();
while(n>0)
{
if(mp[(*i)].block!=flag)
{
int g=(*i);
temp.erase(i);
temp.push_back(g);
left--;
n--;
continue;
}
i++;
n--;
}
return left;
}
int func2(int left, int flag, vector<int> &temp)//计算任务亲和性
{
int left_n=left;
vector<int>::iterator i;
set<int> s;//存储可用区
for(i=temp.begin(); i!=temp.end();i++)
{
if(mp[(*i)].task.count(flag)==1)s.insert(mp[(*i)].block);
}
i=temp.begin();
while(left>0)
{
if(s.find(mp[(*i)].block)==s.end())
{
int g=(*i);
temp.erase(i);
temp.push_back(g);
left_n--;
left--;
continue;
}
i++;
left--;
}
return left_n;
}
vector<int> func3(int left, int flag, vector<int> temp)//计算任务反亲和性
{
int left_n=left;
vector<int>::iterator i=temp.begin();
while(left>0)
{
if(mp[(*i)].task.count(flag)==1)
{
int g=(*i);
temp.erase(i);
temp.push_back(g);
left_n--;
left--;
continue;
}
i++;
left--;
}
temp.push_back(left_n);
return temp;
}
bool cmp(int &a, int &b)//节点结构体的排序
{
if(mp[a].num!=mp[b].num)return mp[a].num<mp[b].num;
else return mp[a].id<mp[b].id;
}
int main()
{
int n=0, m=0;
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
{
int temp=0;
scanf("%d", &temp);
node p={i, temp, 0};
mp[i]=p;
v.push_back(i);
}
int g=0;
scanf("%d", &g);
int a1=0, a2=0, a3=0, a4=0, a5=0, a6=0;
for(int i=1;i<=g;i++)
{
scanf("%d%d%d%d%d%d", &a1, &a2, &a3, &a4, &a5, &a6);
for(int j=1;j<=a1;j++)
{
int left=n;
vector<int> temp1;
temp1.push_back(0);
if(a3!=0)left=func1(left, a3, v);//计算节点亲和性
if(a4!=0)left=func2(left, a4, v);//计算任务亲和性
if(a5!=0)temp1=func3(left, a5, v);//计算任务反亲和性
if(temp1.back()==0&&a6==1||left==0)
{
printf("0 ");
}
else if(temp1.back()==0&&a6==0)
{
sort(v.begin(),v.begin()+left,cmp);
printf("%d ", v[0]);
mp[v[0]].num++;
mp[v[0]].task.insert(a2);
}
else
{
sort(temp1.begin(),temp1.begin()+temp1.back(),cmp);
printf("%d ", temp1[0]);
mp[temp1[0]].num++;
mp[temp1[0]].task.insert(a2);
}
}
printf("\n");
}
return 0;
}
这道题就是个实际问题的大模拟,考验大家的码力和耐心。理解题意之后,一边对着测试样例边看代码应该能理解看懂。有几点需要注意的提醒一下:
1、本题用unordered_map不用map,两者的用法差不多,就是底层的实现不一样,用map过不了,只能得25分—.—。
2、unordered_map是C++11的标准,用Dev-C++的朋友要修改一下编译环境才能运行C++11。
操作如下:
希望能帮助到大家!