LG5763 [NOI1999]内存分配

发布于 2020-08-16  3.3k 次阅读


题面

题意

按照时间顺序给你 $n$ 个申请,每个申请会给出申请发起时间,占用空间大小,占用时间三个参数,程序会尝试在申请发起时分配内存,分配的内存必须是一段连续的,大小满足条件的空间,如果无法在申请发起时直接分配出内存,询问将进入等待队列,在之后的任意时刻一旦空间满足条件队头会直接出队并且程序会分配给队头询问足够的空间。问将所有申请处理完所需要的时间和所有进入过等待队列的申请数量。

分析&代码

按题意模拟,代码题目较难,需要会熟练使用一些常用数据结构。

这里直接结合代码分析。


1.首先我们需要有数据结构来记录内存中的占用情况,这里直接使用 list 实现

struct saveQ  //记载在内存中的询问占用情况
{
    int begin, end, finish;  //在内存中的起始位置,终止位置,结束时间
};

list<saveQ> id;  //存放内存的中的询问占用情况

2.我们还需要有数据结构来存放等待队列,这里直接用 queue 实现

struct ques  //记录待处理询问
{
    int m, p;  //需要的内存数量,占用时间
};

queue<ques> que;  //存放询问的缓存序列

3.由于我们发现询问的结束时间可能很大,这里使用 priority_queue 来存放当前内存中所有询问的结束时间,在接下来的操作中可以通过直接取出堆顶的方式来获知当前所有内存中的询问中最早的结束时间。

priority_queue<int, vector<int>, greater<int> > prQue;  //记录每一个询问的结束时间

4.这里写一个函数,用来查询当前内存中是否有大小为 $m$ 的空间,如果有,返回 true 并插入 list 中,否则返回 false 。

bool work(int m, int p) {  //查询内存中是否有连续长度为m的空间,占用时长为p
    list<saveQ>::iterator ed = id.end();
    ed--;
    for (list<saveQ>::iterator i = id.begin(); i != ed; i++) {  //i来遍历出一个占用块
        list<saveQ>::iterator nxt = i;
        nxt++;                               //用nxt来确定i占用块之后的下一个占用块
        if (nxt->begin - i->end - 1 >= m) {  //若两个占用块之间空余可以放下这个新块
            id.insert(nxt, (saveQ){i->end + 1, i->end + m, Time + p});
            prQue.push(Time + p);  //将新加入的询问的信息计入优先队列中
            return true;
        }
    }
    return false;
}

5.在程序中我们维持一个变量 int Time 表示当前时间,在一个新询问加入时,我们需要先将当前时间推到询问开始时间,然后再处理询问的问题,所以这里提供一个函数来帮我们将时间推移。

void timeSet(int tim) {
    while (!prQue.empty() && prQue.top() <= tim)  //如果当前将最早完成的占用块将在t之前结束,则处理
    {
        Time = prQue.top();  //将时间推到该占用块结束的时刻
        prQue.pop();
        for (list<saveQ>::iterator i = id.begin(); i != id.end(); i++)
            while (i != id.end() && i->finish == Time)  //将在当前时间结束的占用块都清除
                i = id.erase(i);
        while (!que.empty()) {  //尝试给在队列中等待的块分配内存空间
            ques cac = que.front();
            if (work(cac.m, cac.p))  //成功分配
                que.pop();           //块出队
            else
                break;
        }
    }
    return;
}

不过这里有一个细节,由于我最开始的不注意,我在这份代码的第7行中并没有写 while 而是写了一个 if,毕竟优先队列里的每一个数对应的是一个询问,但是我后来发现这样的操作会使得原本同一时间结束的询问一个一个结束,在部分询问结束,而部分询问还未结束的情况下可能部分询问已经从队列中出来并且取到了非最优的位置。所以这里应该写 while,一次性结束完所有该时刻结束的询问。


6.最后就是主程序了,有一些细节,在代码中已注明。

int main() {
    int n;
    scanf("%d", &n);
    id.push_back((saveQ){-1, -1, 0x7fffffff}), id.push_back((saveQ){n, n, 0x7fffffff});
    //链表内插入头和尾节点以防止越界
    int t, m, p, num = 0;
    while (true) {
        scanf("%d%d%d", &t, &m, &p);
        if (!m)
            break;
        timeSet(t);
        Time = t;         //因为如果等待队列为空会有提早结束的情况,所以重复赋值一遍
        if (!work(m, p))  //如果放不进这一个新块,则加入等待队列
            que.push((ques){m, p}), num++;
    }
    timeSet(0x7ffffffe);  //将时间往后推直至最后一个块也解除占用
    printf("%d\n%d\n", Time, num);
    return 0;
}

我缓慢吐出一串啊吧啊吧并不再想说话