題目背景
小 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
。
樣例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輸入
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
下的孩子文件還是后代文件,都受到后代配額的限制,因此兩條指令執行都不成功。
子任務
本題目各個測試點的數據規模如下:
表格中,目錄層次是指各指令中出現的路徑中,/
字符的數目。
所有輸入的數字均不超過\(10^{18}\)。
轉載:https://blog.csdn.net/weixin_43693379/article/details/112725802
代碼
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
const int N = 5e6 + 10;
typedef long long ll;
//LDd目錄配額,LRd后代配額,LD現有目錄額,LR現有后代額
struct node {
map<string, int> branch; //每個目錄節點的孩子節點(包括目錄節點和普通文件)
int isleaf; //是否是普通文件
ll LDd, LRd; //目錄配額,后代配額
ll LD, LR; //現有目錄,后代
ll val; //普通文件的大小
int fa; //父節點編號
};
node tr[N];
int vex; //有效文件名所屬編號
string getindex(string &s, int &num) //截取一個合法文件名
{
string res = "";
int i;
for (i = num; s[i] != '/' && i < s.size(); i++)
res += s[i];
num = i + 1;
return res;
}
vector<pair<int, string> > v;
void del() //若插入失敗,對本次前面所有插入操作進行撤銷
{
for (int i = 0; i < v.size(); i++) {
int a = v[i].first;
string b = v[i].second;
tr[a].branch.erase(tr[a].branch.find(b));
}
}
bool insert(string s, ll size) //文件插入
{
v.clear();
int num = 1, fa = 0, init = vex, len = s.size();
while (num < len) //直至截取最后一個文件名
{
string ss = getindex(s, num);
//如果要創建的目錄文件與已有的同一雙親目錄下的孩子文件中的普通文件名稱重復,則該指令不能執行成功
if (tr[fa].branch[ss] && tr[tr[fa].branch[ss]].isleaf && num < len) {
vex = init;
del();
return false;
}
int id, flag = 0;
if (tr[fa].branch[ss])
id = tr[fa].branch[ss], flag = 1;
else //若不存在該目錄/普通文件,進行創建
id = ++vex, tr[vex].isleaf = 0, tr[vex].fa = fa, tr[vex].LDd = 0, tr[vex].LRd = 0, tr[fa].branch[ss] = vex, v.push_back(
make_pair(fa, ss));
if (num < len)
fa = id;
if (num >= len) //遍歷至葉子文件時
{
ll extra; //需要更新的文件值
if (flag) //葉子文件如果已存在
{
if (!tr[id].isleaf) //如果是個目錄文件,插入失敗
{
vex = init;
del();
return false;
}
extra = size - tr[id].val;
} else
extra = size;
if (tr[fa].LD + extra > tr[fa].LDd && tr[fa].LDd) //如果雙親目錄的目錄配額存在且因為插入該文件超額,插入失敗
{
vex = init;
del();
return false;
}
tr[id].val = size;
tr[id].LR = size;
tr[id].isleaf = 1;
for (int r = fa; r != -1; r = tr[r].fa)
if (tr[r].LR + extra > tr[r].LRd && tr[r].LRd) //如果后代配額存在且會因為插入此文件超額,插入失敗
{
vex = init;
del();
return false;
}
for (int r = fa; r != -1; r = tr[r].fa) //插入成功,更新整條路徑
tr[r].LR += extra;
tr[fa].LD += extra;
}
}
return true;
}
void remove(string s) //移除文件
{
int num = 1, fa = 0, len = s.size();
while (num < len) {
string ss = getindex(s, num);
if (!tr[fa].branch[ss]) return; //若路徑不存在,直接返回
int id = tr[fa].branch[ss];
if (num < len)
fa = id;
if (num < len && tr[id].isleaf) //如果路徑不合法,返回
return;
if (num >= len) {
tr[fa].branch.erase(tr[fa].branch.find(ss)); //刪除與雙親節點之間的路徑
for (int r = fa; r != -1; r = tr[r].fa) //更新前面祖宗節點的后代配額
tr[r].LR -= tr[id].LR;
if (tr[id].isleaf) //若刪除節點為葉子節點,更新雙親節點的目錄配額
tr[fa].LD -= tr[id].LR;
}
}
}
bool set(string s, ll ld, ll lr) //設置配置額
{
if (s.size() == 1) //配置根目錄
{
int id = 0;
if ((ld < tr[id].LD && ld) || (lr < tr[id].LR && lr)) //如果配置額小於當前已存在的文件大小,配置失敗
return false;
tr[id].LRd = lr;
tr[id].LDd = ld;
return true;
}
int num = 1, fa = 0, len = s.size();
while (num < len) {
string ss = getindex(s, num);
if (!tr[fa].branch[ss]) return false; //如果路徑不存在,配置失敗
int id = tr[fa].branch[ss];
fa = id;
if (tr[id].isleaf) return false; //如果配置的不是目錄文件,配置失敗
if (num >= len) {
if ((ld < tr[id].LD && ld) || (lr < tr[id].LR && lr)) //如果配置額小於當前已存在的文件大小,配置失敗
return false;
tr[id].LRd = lr;
tr[id].LDd = ld; //修改額度,配置成功
return true;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
string s;
int n;
cin >> n;
tr[0].fa = -1;
tr[0].isleaf = 0;
tr[0].LDd = 0;
tr[0].LRd = 0;
while (n--) {
bool flag = true;
char order;
cin >> order;
ll ld, lr, size;
switch (order) {
case 'C':
cin >> s >> size;
flag = insert(s, size);
break;
case 'R':
cin >> s;
remove(s);
break;
case 'Q':
cin >> s >> ld >> lr;
flag = set(s, ld, lr);
break;
}
if (flag)
cout << "Y" << '\n';
else
cout << "N" << '\n';
}
return 0;
}