文章目錄
- Background
- Problem
- Input
- Output
- Sample
- 題意
- 解題思路
- 進一步的討論
Timus 1037. Memory management 要求你實現一個記憶體管理器。
1037. Memory management
Time Limit: 2.0 second
Memory Limit: 16 MB
Background
Don't you know that at school pupils’ programming contest a new computer language has been developed. We call it D++. Generally speaking it doesn't matter if you know about it or not. But to run programs written in D++ we need a new operating system. It should be rather powerful and complex. It should work fast and have a lot of possibilities. But all this should be done in a future.
And now you are to… No. You should not devise the name for the operating system. You are to write the first module for this new OS. And of course it's the memory management module. Let's discuss how it is expected to work.
Problem
Our operating system is to allocate memory in pieces that we’ll call “blocks”. The blocks are to be numbered by integers from 1 up to N. When operating system needs more memory it makes a request to the memory management module. To process this request the memory management module should find free memory block with the least number. You may assume that there are enough blocks to process all requests.
Now we should define the meaning of words “free block”. At the moment of first request to the memory management module all blocks are considered to be free. Also a block becomes free when there were no requests to it during T minutes.
You may wonder about a notion “request to allocated blocks”. What does it mean, “request to allocated block”? The answer is simple: at any time the memory management module may be requested to access a given block. To process this request the memory management module should check if the requested block is really allocated. If it is, the request is considered to be successful and the block remains allocated for T minutes more. Otherwise the request is failed.
That's all about the algorithms of the memory management block. You are to implement them for N = 30 000 and T = 10 minutes.
Input
Each line of input contains a request for memory block allocation or memory block access. Memory allocation request has a form:
<Time> +
where <Time> is a nonnegative integer number not greater than 65 000. Time is given in seconds. Memory block access request has a form:
<Time> . <BlockNo>
where <Time> meets conditions mentioned above for the memory allocation request and <BlockNo> is an integer value in range from 1 to N. There will be no more than 80000 requests.
Output
For each line of input you should print exactly one line with a result of request processing. For memory allocation request you are to write an only integer — a number of allocated block. As it was mentioned above you may assume that every request can be satisfied, there will be no more than N simultaneously allocated blocks. For memory block access request you should print the only character:
- '+' if request is successful (i.e. block is really allocated);
- '-' if request fails (i.e. block with number given is free, so it can't be accessed).
Requests are arranged by their times in an increasing order. Requests with equal times should be processed as they appear in input.
Sample
| input |
output |
1 + 1 + 1 + 2 . 2 2 . 3 3 . 30000 601 . 1 601 . 2 602 . 3 602 + 602 + 1202 . 2 |
1 2 3 + + - - + - 1 3 - |
Problem Author: Alexander Klepinin
Problem Source: Ural State University Internal Contest October'2000 Students Session
題意
一門新的程式設計語言 D++ 被開發出來,用於在校學生參加的程式設計競賽。但是需要一個新的作業系統才能運行 D++ 語言寫的程式。現在你需要為新的作業系統寫第一個模組:記憶體管理。
該作業系統以“塊”為單位分配記憶體,“塊”從 1 到 N 進行編號。當作業系統需要記憶體時就向記憶體管理模組發送一個請求。記憶體管理模組收到該請求後將分配編號最小的“自由塊”。
任何已指派的“記憶體塊”如果在 T 分鐘之內沒有收到訪問請求,將被釋放,成為“自由塊”。
任何時刻作業系統都可以請求訪問一個給定編號的“記憶體塊”。如果該“記憶體塊”尚未分配,該請求將失敗。否則,該請求成功,並且將該“記憶體塊”保持 T 分鐘不被釋放。
輸入的每一行包含一個記憶體配置請求或者記憶體訪問請求。記憶體配置請求的如下所示:
<Time> +
記憶體訪問請求如下所示:
<Time> . <BlockNo>
這裡 <Time> 是一個不大於 65,000 的非負整數,表示請求的時間。<BlockNo> 是一個 1 到 N 之間的整數,表示所要訪問的記憶體塊的編號。
對應每一行輸入都有一行輸出。對於記憶體配置請求,輸出分配的“記憶體塊”編號。對於記憶體訪問請求,如果成功,輸出“+”號,否則,輸出“-”號。
解題思路
在程式的開始將建立一個儲存已經分配的“記憶體塊”的優先隊列。所以,需要一個資料結構來表示“記憶體塊”,如下所示:
struct Block{ public int Id { get; private set; } public int Time { get; set; } public Block(int id, int time) : this() { Id = id; Time = time; }}
這裡 Id 表示“記憶體塊”的編號,Time 表示該“記憶體塊”到期時間(過了這個時間該“記憶體塊”將被釋放)。
由於到期時間早的“記憶體塊”將先出隊,所以應該是“小者優先”,而不是通常的“大者優先”。因此,需要一個逆序的“比較子”,如下所示:
sealed class TimeComparer : IComparer<Block>{ public int Compare(Block x, Block y) { return (x.Time == y.Time) ? 0 : ((x.Time < y.Time) ? 1 : -1); }}
接著,將建立一個儲存“自由塊”編號的優先隊列。由於該記憶體管理模組將分配編號最小的“自由塊”,因此,也需要一個逆序的“比較子”,如下所示:
sealed class IdComparer : IComparer<int>{ public int Compare(int x, int y) { return (x == y) ? 0 : ((x < y) ? 1 : -1); }}
現在,輪到主程式上場了:
using System;using System.Collections.Generic;using Skyiv.Util;namespace Skyiv.Ben.Timus{ // http://acm.timus.ru/problem.aspx?space=1&num=1037 sealed class T1037 { static void Main() { const int T = 10 * 60, N = 30000; var used = new KeyedPriorityQueue(new TimeComparer()); var free = new PriorityQueue<int>(new IdComparer()); for (var i = 1; i <= N; i++) free.Push(i); for (string s; (s = Console.ReadLine()) != null; ) { var ss = s.Split(); int id, time = int.Parse(ss[0]); for (Block v; used.Count > 0 && (v = used.Top()).Time <= time; used.Pop()) free.Push(v.Id); if (ss.Length == 2) { used.Push(new Block(id = free.Pop(), time + T)); Console.WriteLine(id); } else if (ss.Length == 3) { var hited = used.ContainsKey(id = int.Parse(ss[2])); if (hited) used.Update(new Block(id, time + T)); Console.WriteLine(hited ? "+" : "-"); } } } }}
這段代碼是非常簡單的。在 Main 方法的主迴圈中按行讀取輸入,擷取請求的時間(time = int.Parse(ss[0])), 接著用一個迴圈將 used 隊列中所有已經到期的“記憶體塊”出隊(used.Pop()),並將其編號(Id)加入到 free 隊列中(free.Push(v.Id))。
接著,如果是請求分配記憶體(ss.Length == 2),則從 free 隊列中出隊,並加入到 used 隊列中去(used.Push(new Block(id = free.Pop(), time + T))),然後輸出該“記憶體塊”的編號即可(Console.WriteLine(id))。
如果是記憶體訪問請求(ss.Length == 3),則檢查要訪問的“記憶體塊”是否在 used 隊列中(hited = used.ContainsKey(id = int.Parse(ss[2]))),如果是的話,就更新其到期時間(used.Update(new Block(id, time + T)))。最後則輸出結果(Console.WriteLine(hited ? "+" : "-"))。
現在,輪到儲存已經分配的“記憶體塊”的優先隊列 KeyedPriorityQueue 上場了:
class KeyedPriorityQueue{ Dictionary<int, int> keys; IComparer<Block> comparer; Block[] heap; public int Count { get; private set; } public KeyedPriorityQueue(IComparer<Block> comparer) { this.keys = new Dictionary<int, int>(); this.comparer = (comparer == null) ? Comparer<Block>.Default : comparer; this.heap = new Block[16]; } public bool ContainsKey(int id) { return keys.ContainsKey(id); } public void Update(Block v) { if (!ContainsKey(v.Id)) throw new ArgumentOutOfRangeException("v", v, "更新優先隊列時無此健值"); var cmp = comparer.Compare(v, heap[keys[v.Id]]); heap[keys[v.Id]].Time = v.Time; if (cmp < 0) SiftDown(keys[v.Id]); else if (cmp > 0) SiftUp(keys[v.Id]); } public void Push(Block v) { if (Count >= heap.Length) Array.Resize(ref heap, Count * 2); heap[keys[v.Id] = Count++] = v; SiftUp(keys[v.Id]); } public Block Pop() { var v = Top(); keys.Remove(v.Id); heap[0] = heap[--Count]; if (Count > 0) SiftDown(keys[heap[0].Id] = 0); return v; } public Block Top() { if (Count > 0) return heap[0]; throw new InvalidOperationException("優先隊列為空白"); } void SiftUp(int n) { var v = heap[n]; for (var n2 = n / 2; n > 0 && comparer.Compare(v, heap[n2]) > 0; n = n2, n2 /= 2) heap[keys[heap[n2].Id] = n] = heap[n2]; heap[keys[v.Id] = n] = v; } void SiftDown(int n) { var v = heap[n]; for (var n2 = n * 2; n2 < Count; n = n2, n2 *= 2) { if (n2 + 1 < Count && comparer.Compare(heap[n2 + 1], heap[n2]) > 0) n2++; if (comparer.Compare(v, heap[n2]) >= 0) break; heap[keys[heap[n2].Id] = n] = heap[n2]; } heap[keys[v.Id] = n] = v; }}
如上所示,KeyedPriorityQueue 類和我在上一篇隨筆“用 C# 實現優先隊列”中實現的 PriorityQueue<T> 類非常相像。這兩者的演算法是一樣的,都是用“堆(heap)”來實現優先隊列。
KeyedPriorityQueue 用一個字典(Dictionary<int, int> keys)來儲存“記憶體塊”的編號(Id)及其在堆中的索引,以便判斷給定的“記憶體塊”是否在隊列中(bool ContainsKey(int id)),並且根據需要更新其到期時間(Update(Block v))。
其他方法,Push、Pop、Top、SiftUp、SiftDown,除了加入對字典 keys 的處理以外,和 PriorityQueue<T> 類是一樣的。
進一步的討論
這個程式中使用 KeyedPriorityQueue 來儲存已指派的“記憶體塊”,使用 PriorityQueue<T> 來儲存尚未分配的“自由塊”。這兩個優先隊列的演算法是一樣的,可以想辦法合并。這將在下一篇隨筆中討論。
這道題中假設了問題的規模滿足以下限制條件:
- “記憶體塊”的總數目
N 不超過 30,000
- 時間
Time 不超過 65,000
- 請求的總數不超過 80,000
我們在程式中只使用第一個限制條件,即在 Main 方法的第四行中將所有的“記憶體塊”都加入到儲存“自由塊”的優先隊列 free 中:
for (var i = 1; i <= N; i++) free.Push(i);
實際上,這個限制條件也很容易繞過的。我們可以修改儲存“自由塊”的優先隊列,如下所示:
sealed class MyPriorityQueue{ int id = 1; PriorityQueue<int> queue; public MyPriorityQueue(IComparer<int> comparer) { queue = new PriorityQueue<int>(comparer); } public void Push(int v) { queue.Push(v); } public int Pop() { return (queue.Count == 0) ? id++ : queue.Pop(); }}
然後,將 Main 方法的第三行中的 PriorityQueue<int> 替換為 MyPriorityQueue,並刪除第四行和第一行中的 N = 30000 即可。
因為在 MyPriorityQueue 類的 Pop 方法中,一旦發現該優先隊列為空白,就返回欄位 id 的值,並使 id 的值增一。這個 id 就是我們將要分配的最小“自由塊”的編號。
這樣,我們的記憶體管理模組就可以無視題目的限制條件,在問題的規模超過這些限制條件時仍然能夠正常工作。
返回目錄