http://hihocoder.com/contest/hiho62/problem/1
題意分析
在瀏覽網頁的時候,緩存技術能夠迅速地顯示頁面。這裏我們對瀏覽器的緩存技術進行簡化:我們認爲瀏覽器的緩存大小爲M,表示緩存可以存儲M個頁面。
當用戶訪問URL時,瀏覽器會先到緩存中查詢是否有該頁面的記錄,如果有則直接從緩存中提取數據;否則,會發送網絡請求,從Internet獲取該頁面,並將該頁面放入緩存中。當緩存中的頁面數量超過M時,緩存技術會替換掉緩存中最不頻繁訪問的網頁,即最後訪問時間最早的頁面。
現在,我們希望對於給定的用戶訪問歷史記錄,請判斷出每一次訪問頁面是從緩存中讀取的,還是通過網絡請求獲得的。(緩存最開始數據爲空)
算法分析
本題的實質是模擬緩存的工作。
簡單的算法
我們直接純粹的模擬緩存,每當訪問一個新的頁面時,我們去遍歷現在的m條緩存記錄。一是確認當前瀏覽的頁面URL是否存在於緩存記錄中,二是找出當前緩存記錄中最早的一條,並將其替換。
每一次操作的時間複雜度爲O(M),由於一共有N條記錄,所以總的時間複雜度爲O(NM)。
優化
首先我們可以知道O(NM)中的N是沒有辦法優化的,所以我們只能從每一次緩存更新入手。
緩存的更新分爲兩個部分:
檢查當前是否存在於緩存
將緩存記錄中最早的一條進行替換
檢查當前頁面是否存在於緩存
這一個部分我們可以簡單的使用map來實現,簡單的用Boolean(bool)類型來表示該URL是否在緩存中即可。
將緩存記錄中最早的一條進行替換
之前我們採取的方法是通過O(M)的時間進行遍歷來查找,如果我們將緩存換用另一種數據結構來進行存儲就可以做到O(logN)。
比如二叉樹、小根堆等都可以做到。
在這裏打算講的是一種只使用map來完成緩存的更新。
首先我們用a[1…n]來表示瀏覽網頁的記錄,用map< url, int > lastVisit的方式來對URL進行記錄。
lastVisit[ a[i] ]表示a[i]這條url最後一次訪問時對應的時間。
我們知道緩存的大小爲M,而且滿足了緩存中一定是M個不相同的URL。
假設在我們現在的緩存中最早的一條記錄對應的爲a[s],最新的一條記錄對應的是a[t]。這樣對於第一個問題,檢查當前頁面是否存在於緩存,我們只需要判定當前i是否在[s,t]這個區間中。
同時可以證明在a[s…t]中至多包含有m條不同的URL。
其證明:
假設a[s..t]中包含有m+1條不同的記錄,則我們加入第m+1條記錄時,在緩存中是無法找到已經存在的記錄。我們一定會拋棄掉緩存中最早的那條記錄a[s],並將其替換爲第m+1條記錄。所以a[s..t]中一定至多包含m條不同的URL記錄。
對於s,t的維護:
t一定總是等於i的,因爲a[i]一定是最新的訪問記錄。
而對於s,我們一定有 lastVisit[ a[s] ] == s。因爲若lastVisit[ a[s] ] = k,則一定有k>s,表示該URL在[s+1,t]之中仍存在。若lastVisit[ a[s] ] = s,則表示在[s+1,t]這個區間內都沒有a[i]的訪問記錄了,a[s]則一定是[s,t]這個區間中最早的訪問記錄。
並且,若此時將a[s]從區間[s,t]中拋棄則表示將這個URL從緩存中拋棄。
所以我們總是要維護lastVisit[ a[s] ] == s。
我們舉個例子來說明:
該樣例中n=9, m=3。小寫的abcd表示4個不同的url。左邊表示我們的訪問記錄,右邊表示我們的LastVisit。
當進行到某一步時,S=1,T=5。
此時我們移動T+1,由於第6個網頁爲d,並不在[s,t]中且m已經到達上限,所以我們需要將a彈出,s+1。同時更新LastVisit[d],得到中間的一步。
接着我們需要維護s,由於LastVisit[b] = 4, LastVisit[c] = 5,所以2,3位的b和c會被跳過,直到s=4,此時有LastVisit[b] = 4。
這樣得到最下面的一幅圖,[s,t]完成了更新。
僞代碼
s = 1
For i = 1..n
If (lastVisit[ a[i] ] in [s,i])
Print "Cache"
Else
cacheCnt = cacheCnt + 1;
If (cacheCnt > m) // 緩存已滿
s = s + 1 // 將該a[s]從cache中拋棄
End If
Print "Internet"
End
lastVisit[ a[i] ] = i // 更新最後一次訪問時間
While (lastVisit[ a[s] ] != s)
s = s + 1
End While // 更新s找到新的最早的訪問記錄
End For
時間複雜度方面,由於使用了map來維護,所以總的時間複雜度爲O(NlogN)
結果分析
該題目的現場通過率爲37.5%。200人提交通過了75名選手。
作爲該場比賽的第一題,通過率並不是特別高。而本題實際的難度並不大,如果直接從複雜度分析入手應該也能夠很容易推到出可行的辦法。
AC代碼:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int Mod = 1e9 + 7;
const int maxn = 2e4 + 5;
const double eps = 0.00000001;
const int INF = 0x3f3f3f3f;
map<string, int> mp;
string st[maxn];
int main()
{
int n, m;
cin >> n >> m;
int s, cacheCnt = 0;
s = 1;
for (int i = 1; i <= n; i ++) {
cin >> st[i];
//if(!mp[st[i]]) mp[st[i]] = i;
if(mp[st[i]] < i && mp[st[i]] >= s) {
cout << "Cache\n";
}else {
cacheCnt ++;
if(cacheCnt > m) s = s + 1;
cout << "Internet\n";
}
mp[st[i]] = i;
while(mp[st[s]] != s) s ++;
}
return 0;
}