帶配額的文件系統
題面
小 H 同學發現,他維護的存儲系統經常出現有人用機器學習的訓練數據把空間占滿的問題,十分苦惱。
查找了一陣資料后,他想要在文件系統中開啟配額限制,以便能夠精確地限制大家在每個目錄中最多能使用的空間。
文件系統概述
文件系統,是一種樹形的文件組織和管理方式。
在文件系統中,文件是指用名字標識的文件系統能夠管理的基本對象,分為普通文件和目錄文件兩種,目錄文件可以被簡稱為目錄。
目錄中有一種特殊的目錄被叫做根目錄。
除了根目錄外,其余的文件都有名字,稱為文件名。
合法的文件名是一個由若干數字([0−9])、大小寫字母([A−Za−z])組成的非空字符串。
普通文件中含有一定量的數據,占用存儲空間;目錄不占用存儲空間。
文件和目錄之間存在含於關系。
上述概念滿足下列性質:
有且僅有一個根目錄;
對於除根目錄以外的文件,都含於且恰好含於一個目錄;
含於同一目錄的文件,它們的文件名互不相同;
對於任意不是根目錄的文件 f,若 f 不含於根目錄,那么存在有限個目錄 d1,d2,…,dn,使得 f 含於 d1,d1 含於 d2,…,dn 含於根目錄。
結合性質 4 和性質 2 可知,性質 4 中描述的有限多個目錄,即諸 di,是唯一的。
再結合性質 3,我們即可通過從根目錄開始的一系列目錄的序列,來唯一地指代一個文件。
我們記任意不是根目錄且不含於根目錄的文件 f 的文件名是 Nf,那么 f 的路徑是:‘/′+Ndn+‘/′+⋯+Nd1+‘/′+Nf,其中符號 + 表示字符串的連接;對於含於根目錄的文件 f,它的路徑是:‘/′+Nf;根目錄的路徑是:‘/′。
不符合上述規定的路徑都是非法的。
例如:/A/B
是合法路徑,但 /A//B、/A/、A/、A/B
都不是合法路徑。
若文件 f 含於目錄 d,我們也稱 f 是 d 的孩子文件。
d 是 f 的雙親目錄。
我們稱文件 f 是目錄 d 的后代文件,如果滿足:(1) f 是 d 的孩子文件,或(2)f 含於 d 的后代文件。
如圖所示,該圖中繪制的文件系統共有\(8\)個文件。
其中,方形表示目錄文件,圓形表示普通文件,它們之間的箭頭表示含於關系。
在表示文件的形狀上的文字是其文件名;各個形狀的左上方標記了序號,以便敘述。
在該文件系統中,文件 5 含於文件 2,文件 5 是文件 2 的孩子文件,文件 5 也是文件 2 的后代文件。
文件 8 是文件 2 的后代文件,但不是文件 2 的孩子文件。
文件 8 的路徑是 /D1/D1/F2
。
配額概述
配額是指對文件系統中所含普通文件的總大小的限制。
對於每個目錄 d,都可以設定兩個配額值:目錄配額和后代配額。
我們稱目錄配額 LDd 是滿足的,當且僅當 d 的孩子文件中,全部普通文件占用的存儲空間之和不大於該配額值。
我們稱后代配額 LRd 是滿足的,當且僅當 d 的后代文件中,全部普通文件占用的存儲空間之和不大於該配額值。
我們稱文件系統的配額是滿足的,當且僅當該文件系統中所有的配額都是滿足的。
很顯然,若文件系統中僅存在目錄,不存在普通文件,那么該文件系統的配額一定是滿足的。
隨着配額和文件的創建,某個操作會使文件系統的配額由滿足變為不滿足,這樣的操作會被拒絕。
例如:試圖設定少於目前已有文件占用空間的配額值,或者試圖創建超過配額值的文件。
在本題中,假定初始狀態下,文件系統僅包含根目錄。
你將會收到若干對文件系統的操作指令。
對於每條指令,你需要判斷該指令能否執行成功,對於能執行成功的指令,在成功執行該指令后,文件系統將會被相應地修改。
對於不能執行成功的指令,文件系統將不會發生任何變化。
你需要處理的指令如下:
創建普通文件
創建普通文件指令的格式如下:
C <file path> <file size>
創建普通文件的指令有兩個參數,是空格分隔的字符串和一個正整數,分別表示需要創建的普通文件的路徑和文件的大小。
對於該指令,若路徑所指的文件已經存在,且也是普通文件的,則替換這個文件;若路徑所指文件已經存在,但是目錄文件的,則該指令不能執行成功。
當路徑中的任何目錄不存在時,應當嘗試創建這些目錄;若要創建的目錄文件與已有的同一雙親目錄下的孩子文件中的普通文件名稱重復,則該指令不能執行成功。
另外,還需要確定在該指令的執行是否會使該文件系統的配額變為不滿足,如果會發生這樣的情況,則認為該指令不能執行成功,反之則認為該指令能執行成功。
移除文件
移除文件指令的格式如下:
R <file path>
移除文件的指令有一個參數,是字符串,表示要移除的文件的路徑。
若該路徑所指的文件不存在,則不進行任何操作。
若該路徑所指的文件是目錄,則移除該目錄及其所有后代文件。
在上述過程中被移除的目錄(如果有)上設置的配額值也被移除。
該指令始終認為能執行成功。
設置配額值
Q <file path> <LD> <LR>
設置配額值的指令有三個參數,是空格分隔的字符串和兩個非負整數,分別表示需要設置配額值的目錄的路徑、目錄配額和后代配額。
該指令表示對所指的目錄文件,分別設置目錄配額和后代配額。
若路徑所指的文件不存在,或者不是目錄文件,則該指令執行不成功。
若在該目錄上已經設置了配額,則將原配額值替換為指定的配額值。
特別地,若配額值為 0,則表示不對該項配額進行限制。
若在應用新的配額值后,該文件系統配額變為不滿足,那么該指令執行不成功。
輸入格式
輸入的第一行包含一個正整數\(n\),表示需要處理的指令條數。
輸入接下來會有\(n\)行,每一行一個指令。
指令的格式符合前述要求。
輸入數據保證:對於所有指令,輸入的路徑是合法路徑;對於創建普通文件和移除文件指令,輸入的路徑不指向根目錄。
輸出格式
輸出共有\(n\)行,表示相應的操作指令是否執行成功。若成功執行,則輸出字母\(Y\);否則輸出\(N\)。
數據范圍
本題目各個測試點的數據規模如下:
表格中,目錄層次是指各指令中出現的路徑中,/ 字符的數目。
所有輸入的數字均不超過\(10^{18}\)。
輸入樣例1:
10
C /A/B/1 1024
C /A/B/2 1024
C /A/B/1/3 1024
C /A 1024
R /A/B/1/3
Q / 0 1500
C /A/B/1 100
Q / 0 1500
R /A/B
Q / 0 1
輸出樣例1:
Y
Y
N
N
Y
N
Y
Y
Y
Y
樣例1解釋
輸入總共有\(10\)條指令。
其中前兩條指令可以正常創建兩個普通文件。
第三條指令試圖創建 /A/B/1/3
,但是 /A/B/1
已經存在,且不是目錄,而是普通文件,不能再進一步創建孩子文件,因此執行不成功。
第四條指令試圖創建 /A
,但是 /A
已經存在,且是目錄,因此執行不成功。
第五條指令試圖刪除 /A/B/1/3
,由於該文件不存在,因此不對文件系統進行修改,但是仍然認為執行成功。
第六條指令試圖在根目錄增加后代配額限制,但此時,文件系統中的文件總大小是 2048,因此該限制無法生效,執行不成功。
第七條指令試圖創建文件 /A/B/1
,由於 /A/B/1
已經存在,且是普通文件,因此該指令實際效果是將原有的該文件替換。
此時文件總大小是\(1124\),因此第八條指令就可以執行成功了。
第九條指令遞歸刪除了 /A/B
目錄和它的所有后代文件。
此時文件系統中已經沒有普通文件,因此第十條命令可以執行成功。
**輸入樣例2:*8
9
Q /A/B 1030 2060
C /A/B/1 1024
C /A/C/1 1024
Q /A/B 1024 0
Q /A/C 0 1024
C /A/B/3 1024
C /A/B/D/3 1024
C /A/C/4 1024
C /A/C/D/4 1024
輸出樣例2:
N
Y
Y
Y
Y
N
Y
N
N
樣例2解釋
輸入共有\(9\)條指令。
第一條指令試圖為 /A/B
創建配額規則,然而該目錄並不存在,因此執行不成功。
接下來的兩條指令創建了兩個普通文件。
再接下來的兩條指令分別在目錄 /A/B
和 /A/C
創建了兩個配額規則。
其中前者是目錄配額,后者是后代配額。
接下來的兩條指令,創建了兩個文件。
其中,/A/B/3
超出了在 /A/B
的目錄配額,因此執行不成功;但 /A/B/D/3
不受目錄配額限制,因此執行成功。
最后兩條指令,創建了兩個文件。
雖然在 /A/C
沒有目錄配額限制,但是無論是 /A/C
下的孩子文件還是后代文件,都受到后代配額的限制,因此兩條指令執行都不成功。
題解
注意到深度只有20, 即最多有\(2\ times 10^6\) 個節點
且每次操作只有20, 直接暴力大模擬即可
對於每個文件(目錄), 建一個結構體, 存儲配額\(ld, lr\) 和已經使用的配額 \(d, r\), 和自己的大小\(g\)(目錄大小為0), 和這個文件的id
存邊用哈希map, unordered_map<ull, node>
, key為文件名的哈希值
刪除文件和重新分配不用說, 純模擬
創建文件, 首先要去找到當前這個文件的大小(不存在為0), 即可獲得將要文件修改的大小
就可以一便判斷當前是否超出配額, 一便更改已使用的配額
代碼
const int N = 2e6 + 5, P = 13331;
struct node { ll ld, lr, d, r, g; int id; };
int n, m, _, k, cas;
unordered_map<ull, node> st[N];
vector<ull> path(string& s) {
vector<ull> p(1, 0);
for (int i = 0; i < s.size(); ++i, p.pb(0))
while (i < s.size() && s[i] != '/') p.back() = p.back() * P + s[i++];
return p.pop_back(), p;
}
PLL Remove(vector<ull>& p, int x, int k) {
auto it = st[x].find(p[k]);
if (it == st[x].end()) return { 0, 0 };
if (k == p.size() - 1) {
queue<int> q; q.push(it->se.id);
while (!q.empty()) {
for (auto& y : st[q.front()]) q.push(y.se.id);
unordered_map<ull, node>().swap(st[q.front()]); q.pop();
}
return st[x].erase(it), PLL{ it->se.g, it->se.r + it->se.g };
}
auto cur = Remove(p, it->se.id, k + 1);
if (cur.fi) it->se.d -= cur.fi, cur.fi = 0;
it->se.r -= cur.se; return cur;
}
ll find(vector<ull>& p, int x, int k) {
auto it = st[x].find(p[k]);
if (it != st[x].end() && it->se.g) return k == p.size() - 1 ? it->se.g : -1;
if (it == st[x].end()) return 0;
if (k == p.size() - 1) return -1;
return find(p, it->se.id, k + 1);
}
bool Create(vector<ull>& p, int x, int k, ll sz) {
auto it = st[x].find(p[k]);
if (k == p.size() - 1 && it != st[x].end()) return it->se.g += sz, 1;
if (k == p.size() - 1) return st[x][p[k]] = { 0, 0, 0, 0, sz, ++n }, 1;
if (it == st[x].end()) it = st[x].insert({ p[k], { 0, 0, 0, 0, 0, ++n}}).fi;
if (k == p.size() - 2 && it->se.ld && it->se.ld - it->se.d < sz) return 0;
if (it->se.lr && it->se.lr - it->se.r < sz) return 0;
bool f = Create(p, it->se.id, k + 1, sz);
if (f && k == p.size() - 2) it->se.d += sz;
if (f) it->se.r += sz;
return f;
}
void R() { string s; cin >> s; auto res = path(s); Remove(res, 0, 0); }
bool C() {
string s; ll sz, lsz; cin >> s >> sz;
auto res = path(s); lsz = find(res, 0, 0);
return ~lsz ? Create(res, 0, 0, sz - lsz) : 0;
}
bool Q() {
string s; ll ld, lr; cin >> s >> ld >> lr;
auto res = path(s);
for (int k = 0, x = 0; k < res.size(); ++k) {
auto it = st[x].find(res[k]);
if (it == st[x].end() || it->se.g) return 0;
if (k == res.size() - 1) {
if ((ld && ld < it->se.d) || (lr && lr < it->se.r)) return 0;
it->se.ld = ld, it->se.lr = lr;
}
x = it->se.id;
}
return 1;
}
int main() {
IOS; st[0][0] = { 0, 0, 0, 0, 0, ++n };
for (cin >> _; _; --_) {
char op; cin >> op; bool f = 1;
if (op == 'C') f = C();
else if (op == 'R') R();
else f = Q();
cout << "NY"[f] << '\n';
}
return 0;
}