今天在CSDN高校俱乐部编程挑战群里面看到了一道据说只有7个人通过的高难度比赛题目,我试着做了一下,但是发现程序效率太低而且只有部分测试用例可以得出正确结果。后来看了题目解析和示例代码,对两种解题思路进行了比较,发现第二种解法更容易理解。现在把题目和代码转载于此,和大家分享:
原文地址:http://student.csdn.net/mcd/topic/235300/937958
题目描述:
在微软云计算服务的机房,有很多机器上跑着一个或者多个的虚拟机。在一段时间里,有很多用户会来请求建立虚拟机,或者把虚拟机关闭。这个时候,一个最重要的问题,是如何把用户的请求分配到不同的机器上。这里我们把实际的问题简化成对CPU的申请。
假定有M台机器用来服务用户,我们把机器编号成0到M。每台机器有多个CPU核,我们把核编号为0到Cm。
当用户在申请资源的时候,会生成一个请求 “申请<k>个核”,并且每个请求编号为m如果我们在现有的机器中能找到一台机器能满足,这台机器的空余的连续的核能满足要求的话,就返回<M, C>作为结果。M是机器的下标,C是申请的第一个核的下标。如果没有找到能满足请求的机器,<-1,-1>作为结果。
当用户释放资源的时候,生成一个请求”第m个请求的资源释放”。保证一个请求释放最多一次。如果请求没有满足,忽略释放的请求。
输入
第一行是T, 总共的测试的个数
每个测试,第一行给出M 和Q,机器的总数和请求的个数
接下来是M行给出每一台机器的核数 Ci
接下来Q行给出请求。请求两种格式,
1. A k 表示申请k个核
2. F m 表示释放第m个请求申请的核
输出
对于每一个测试,首先输出
“Case #i:” i是测试的标号,从1开始。
接下来对于所有申请的请求,输出m c 或者-1 -1
[限制条件]
1 <= T <= 20
1 <= M <= 100000
1 <= Ci <= 128
1 <= Q <= 1000000
1 <= k <= 128
1 <= m <= M
考虑到数据范围,共有10W个机器,100W次查询,时间上不足以在每次询问中遍历所有的机器,但考虑到CPU的数量只有128,可以从这里入手加快查询效率。
我们维护128个集合,每个集合存储不同的最长连续空闲核数的机器,(eg, 集合1存最长空闲数为1的机器,集合2存最长空闲数为2的机器)。对于每次A询问,申请核数为k,我们只需枚举从k到128的所有集合中机器编号最小的,为了查找效率,我们可以使用c++ STL的set (或者java的TreeSet), 内部是树形结构,每个集合的第一个元素即为最小元素,查找到之后暴力更新这个机器使用情况并把新的机器信息加入到集合中,同时为了F操作保存询问信息。对于F操作相对简单,直接恢复记录的信息并更新机器信息就可以了。
此方法每次询问的复杂度大约为O(128*log(n))。
代码:
#include <iostream> #include <cstdio> #include <vector> #include <set> #include <cstring> using namespace std; const int N = 100005; const int M = 130; const int inf = 1e9; struct Query{ //保存每一次询问的结果,id为机器编号,start为CPU起点,end为CPU终点 int id, start, end; Query (){} Query (int a, int b, int c):id(a), start (b), end (c){} }; bool a[N][M]; //记录每个机器CPU的使用情况 int sz[N]; //记录每个机器CPU数 /* * 返回当前机器中连续num个空闲核的起点 */ int getStart (bool used[], int size, int num){ int now = 0; for (int i = 1; i <= size; i ++){ if (!used[i]){ now ++; if (now == num){ return i - now + 1; } }else{ now = 0; } } return -1; } /* * 返回当前机器最大连续空闲核的数量 */ int getMax (bool used[], int size){ int maxx = 0; int t = 0; for (int i = 1; i <= size; i ++){ if (!used[i]){ t ++; maxx = max (maxx, t); }else{ t = 0; } } return maxx; } int main (){ freopen ("1.txt", "r", stdin); freopen ("3.txt", "w", stdout); int T, cas = 1; scanf ("%d", &T); while (T --){ memset (a, 0, sizeof (a)); set<int> se[M]; //se[i]存储的是连续空闲数为i的所有机器编号 vector<Query> vec; //保存历史询问 printf ("Case #%d:\n", cas ++); int n, m; scanf ("%d%d", &n, &m); for (int i = 1; i <= n; i ++){ scanf ("%d", sz + i); se[sz[i]].insert (i); } while (m --){ char op[5]; int t; scanf ("%s%d", op, &t); if (op[0] == 'A'){ int id = inf; for (int i = t; i < M; i ++){ if (se[i].size ()){ id = min (id, *se[i].begin ()); } } if (id == inf){ puts ("-1 -1"); vec.push_back (Query (-1, -1, -1)); }else{ se[ getMax (a[id], sz[id]) ].erase (id); int start = getStart (a[id], sz[id], t); printf ("%d %d\n", id, start); for (int i = start; i <= start + t - 1; i ++){ a[id][i] = true; } vec.push_back (Query (id, start, start + t - 1)); se[ getMax (a[id], sz[id])].insert (id); } }else{ t --; if (vec[t].id == -1) continue; int id = vec[t].id; se[ getMax (a[id], sz[id]) ].erase (id); for (int i = vec[t].start; i <= vec[t].end; i ++){ a[id][i] = false; } se[ getMax (a[id], sz[id]) ].insert (id); } } } }
对于每次询问,我们直接从10W个机器下手,为了提高效率可以使用线段树,线段树的每个节点维护当前区间所有机器最长空闲数,对于A查询,申请核数为k,如果当前节点左儿子值>=k,则在左子树中查询,否则在右子树查询,可以很容易在log(n)时间内查询到所需要的机器。其他操作和上一种解法类似。
代码:
#include <iostream> #include <vector> #include <cstdio> #include <cstring> using namespace std; const int N = 100005; bool b[N][130]; //维护机器使用信息 int size[N]; //机器CPU数 int a[N*4]; //线段树 void pushup (int pos){ a[pos] = max (a[pos*2], a[pos*2+1]); } int query (int l, int r, int pos, int num){ if (l == r){ return l; } int m = (l + r)/2; if (a[pos*2] >= num) return query (l, m, pos*2, num); else return query (m+1, r, pos*2+1, num); } void change (int l, int r, int pos, int P, int val){ if (l == r){ a[pos] = val; return; } int m = (l + r)/2; if (P <= m) change (l, m, pos*2, P, val); else change (m+1, r, pos*2+1, P, val); pushup (pos); } int getMax (bool a[], int n){ int maxx = 0; int t = 0; for (int i = 1; i <= n; i ++){ if (!a[i]){ t ++; maxx = max (maxx, t); }else{ t = 0; } } return maxx; } int getStart (bool a[], int n, int num){ int t = 0; for (int i = 1; i <= n; i ++){ if (!a[i]){ t ++; if (t == num){ return i - num + 1; } }else{ t = 0; } } return n - num + 1; } struct Node{ int a, b, c; Node(int x, int y, int z):a(x), b(y), c(z){} }; int main (){ freopen ("1.txt", "r", stdin); freopen ("2.txt", "w", stdout); int T, cass = 1;; scanf ("%d", &T); while (T --){ memset (b, 0, sizeof (b)); memset (a, 0, sizeof (a)); printf ("Case #%d:\n", cass ++); int n, q; scanf ("%d%d", &n, &q); for (int i = 1; i <= n; i ++){ scanf ("%d", size + i); change (1, n, 1, i, size[i]); } vector<Node> vec; while (q --){ char s[5]; scanf ("%s", s); if (s[0] == 'F'){ int id; scanf ("%d", &id); if (vec[id-1].a == -1) continue; else{ int idPos = vec[id-1].a; for (int i = vec[id-1].b; i <= vec[id-1].c; i ++){ b[idPos][i] = 0; } change (1, n, 1, idPos, getMax (b[idPos], size[idPos])); } }else{ int num; scanf ("%d", &num); if (a[1] < num){ vec.push_back (Node(-1, -1, -1)); printf ("%d %d\n", -1, -1); }else{ int id = query (1, n, 1, num); int pos = getStart (b[id], size[id], num); printf ("%d %d\n", id, pos); for (int i = pos; i <= pos+num-1; i ++){ b[id][i] = true; } change (1, n, 1, id, getMax (b[id], size[id])); vec.push_back (Node(id, pos, pos + num - 1)); } } } } }