目录
前情提要
这题来自于2020年12月ccf第三题,题目逻辑较于上次的DHCP更复杂,也因此我写出了很多bug,手动狗头,嗐,代码不完善,后续填坑。
题目背景
小 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>
None
创建普通文件的指令有两个参数,是空格分隔的字符串和一个正整数,分别表示需要创建的普通文件的路径和
文件的大小。
对于该指令,若路径所指的文件已经存在,且也是普通文件的,
则替换这个文件;若路径所指文件已经存在,但是目录文件的,则该指令不能执行成功。
当路径中的任何目录不存在时,应当尝试创建这些目录;若要创建的目录文件与已有的同一双亲目录下的孩子
文件中的普通文件名称重复,则该指令不能执行成功。
另外,还需要确定在该指令的执行是否会使该文件系统的
配额变为不满足,如果会发生这样的情况,则认为该指令不能执行成功,反之则认为该指令能执行成功。
移除文件
移除文件指令的格式如下:
R <file path>
None
移除文件的指令有一个参数,是字符串,表示要移除的文件的路径。
若该路径所指的文件不存在,则不进行任何操作。
若该路径所指的文件是目录,则移除该目录及其所有后代文件。
在上述过程中被移除的目录(如果有)上设置的配额值也被移除。
该指令始终认为能执行成功。
设置配额值
Q <file path> <LD> <LR>
None
设置配额值的指令有三个参数,是空格分隔的字符串和两个非负整数,分别表示需要设置配额值的目录
的路径、目录配额和后代配额。
该指令表示对所指的目录文件,
分别设置目录配额和后代配额。若路径所指的文件不存在,或者不是目录文件,则
该指令执行不成功。
若在该目录上已经设置了配额,则将原配额值替换为指定的配额值。
特别地,若配额值为 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
Data
样例1输出
Y
Y
N
N
Y
N
Y
Y
Y
Y
Data
样例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
Data
样例2输出
N
Y
Y
Y
Y
N
Y
N
N
Data
样例2解释
输入共有 9 条指令。第一条指令试图为 /A/B
创建配额规则,然而该目录并不存在,因此执行不成功。
接下来的两条指令创建了两个普通文件。再接下来的两条指令分别在目录 /A/B
和 /A/C
创建了
两个配额规则。其中前者是目录配额,后者是后代配额。接下来的两条指令,创建了
两个文件。其中,/A/B/3
超出了在 /A/B
的目录配额,因此执行不成功;但 /A/B/D/3
不受目录配额限制,因此执行成功。最后两条指令,创建了两个文件。虽然在 /A/C
没有目录配额限制,但是无论是 /A/C
下的孩子文件还是后代文件,都受到后代配额的限制,因此两条指令
执行都不成功。
子任务
本题目各个测试点的数据规模如下:
表格中,目录层次是指各指令中出现的路径中,/
字符的数目。
所有输入的数字均不超过1018。
我的思路
思路请见我的代码
下面有两个版本代码
我的代码比较复杂而且还有错误,但是oj上提交对了20%,提示是运行错误,初步估计是堆爆了,但是有空再检查检查,把这个坑填上。也希望大佬帮我看看哪里错了。
代码
满分代码
#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;
}
自己的代码
#include <bits/stdc++.h>
using namespace std;
struct file;
const long long SUP=2e18; //定义一个无穷大
struct dir
{
string name="";
long long size=0; //文件大小
long long fsize=0; //普通文件大小
long long LD=SUP, LR=SUP; //初始化大文件
dir *child_dir=NULL; //孩子文件夹指针
file *child_file=NULL; //孩子文件指针
dir *brother_dir=NULL; //和自己位于同一文件夹下的兄弟文件夹指针
file *brother_file=NULL; //和自己位于同一文件夹下的兄弟文件
};
struct file
{
string name="";
long long size=0;
file *brother_file=NULL;
};
//在当前文件夹下查找文件或文件夹
typedef long long Status;
const Status Find_Dir=1;
const Status Find_File=2;
const Status Error=0;
struct ptr_group
{
dir *dir_ptr=NULL;
file *file_ptr=NULL;
};
Status Search(const dir &nowdir, string obj_name,
ptr_group &find, ptr_group &last) {
find.dir_ptr=NULL; find.file_ptr=NULL;
//找文件夹
dir *child_dir=nowdir.child_dir;
while (child_dir) {
if (child_dir->name==obj_name) {find.dir_ptr=child_dir; return Find_Dir;}
last.dir_ptr=child_dir;
child_dir=child_dir->brother_dir;
}
file *child_file=nowdir.child_file;
while (child_file)
{
if (child_file->name==obj_name) {find.file_ptr=child_file; return Find_File;}
last.file_ptr=child_file;
child_file=child_file->brother_file;
}
return Error;
}
//创建命令
void Creat(queue<string> &path, dir &nowdir, long long final_fliesize, long long &formersize, int &flag) {
formersize=0;
/*###几种不能够执行命令的情况,可以直接退出###*/
//如果标志不用执行该命令,那直接退出
if (flag==0) return;
//要满足LR限制
if (nowdir.size+final_fliesize>nowdir.LR) {flag=0; return;}
//size==2表示到了目标普通文件上一层目录,此时要满足LD
if (path.size()==1&&nowdir.fsize+final_fliesize>nowdir.LD) {flag=0; return;}
//可以创建文件或文件夹,首先获得当前操作文件或文件夹名称
string tem=path.front();
path.pop();
ptr_group find;
ptr_group last;
Status state=Search(nowdir, tem, find, last);
//没有找到目标文件或文件夹,新建一个
if (state==Error) {
if (path.empty()) { //路径队列为空,说明当前已经到最深文件夹了,直接创建文件
file* p=new file;
p->name=tem;
p->size=final_fliesize;
p->brother_file=nowdir.child_file; nowdir.child_file=p; //前插法创建文件
}
else {
dir *p=new dir;
p->name=tem;
p->brother_dir=nowdir.child_dir; nowdir.child_dir=p; //前插法创建文件夹
Creat(path, *p, final_fliesize, formersize, flag);
}
}
//在当前目录下找到了普通文件,替换原来的文件
else if (state==Find_File&&path.empty()) {
formersize=find.file_ptr->size;
find.file_ptr->size=final_fliesize;
}
//要创建一个不存在的目录文件但是和普通文件重名
else if (state==Find_File) flag=0;
//在命令路径下找到了和要创建普通文件的同名文件但是它是目录文件,不操作
else if (state==Find_Dir&&path.empty()) flag=0;
//在命令路径的过程中找到了文件夹但不与最终目标文件同名
else Creat(path, *find.dir_ptr, final_fliesize, formersize, flag);
}
//为了释放删除文件夹的空间
void DeleteTree(dir *delete_root) {
file *delete_file=delete_root->child_file, *tem;
while (delete_file) {
tem=delete_file;
delete_file=delete_file->brother_file;
delete tem;
}
dir *delete_dir=delete_root->child_dir, *met;
while (delete_dir) {
met=delete_dir;
delete_dir=delete_dir->brother_dir;
DeleteTree(met);
}
delete delete_root;
}
//移除文件命令
void Remove(queue<string> &path, dir &root, long long &filesize, int &flag) {
ptr_group find;
ptr_group last;
dir *nowdir=&root;
flag=0;
//判断是否存在该路径
while (!path.empty()) {
string tem=path.front(); path.pop();
Status state=Search(*nowdir, tem, find, last);
if (state==Error) return;
else if (state==Find_File&&path.empty()) { //找到要删除的文件
file *t=find.file_ptr;
last.file_ptr->brother_file=find.file_ptr->brother_file;
filesize=t->size;
delete t;
flag=1; return;
}
else if (state==Find_File&&!path.empty()) return;
else if (state==Find_Dir&&path.empty()) { //找到要删的文件夹
dir *t=find.dir_ptr;
if (last.dir_ptr==NULL) {
filesize=find.dir_ptr->size;
nowdir->child_dir=find.dir_ptr->brother_dir;
DeleteTree(find.dir_ptr);
}
else {
last.dir_ptr->brother_dir=find.dir_ptr->brother_dir;
filesize=find.dir_ptr->size;
DeleteTree(t);
}
flag=2; return;
}
nowdir=find.dir_ptr;
}
}
//Q command
void Qcommand(queue<string> &path, dir &root, long long ld, long long lr, int &flag) {
ptr_group find;
ptr_group last;
dir nowdir=root;
flag=0;
if (ld==0) ld=SUP;
if (lr==0) lr=SUP;
while (!path.empty()) {
string tem=path.front(); path.pop();
Status state=Search(nowdir, tem, find, last);
if (state==Error||state==Find_File) return;
else if (state==Find_Dir&&path.empty()) {
if (find.dir_ptr->size<=lr&&find.dir_ptr->fsize<=ld) {
find.dir_ptr->LR=lr; find.dir_ptr->LD=ld;
flag=1; return;
}
}
nowdir=*find.dir_ptr;
}
if (path.empty()) {
if (root.size<=lr&&root.fsize<=ld) {
root.LD=ld; root.LR=lr;
flag=1; return;
}
}
}
//更新文件夹尺寸和普通文件尺寸
void RenewSize(deque<string> &renew_path, dir *root, long long size, long long formersize,
const string &order, int flag) {
ptr_group find;
ptr_group last;
dir *nowdir=root;
if (order=="C") {
nowdir->size=nowdir->size+size-formersize;
while (renew_path.size()>1) {
string tem=renew_path.front(); renew_path.pop_front();
Search(*nowdir, tem, find, last);
nowdir=find.dir_ptr;
nowdir->size=nowdir->size+size-formersize;
}
nowdir->fsize=nowdir->fsize+size-formersize;
}
else { //order=="R"
if (flag==1) { //删除文件
nowdir->size-=size;
string tem;
while (renew_path.size()>1) {
tem=renew_path.front(); renew_path.pop_front();
Search(*nowdir, tem, find, last);
nowdir=find.dir_ptr;
nowdir->size-=size;
}
nowdir->fsize-=size;
}
else if (flag==2) {
nowdir->size-=size;
string tem;
while (renew_path.size()>1) {
tem=renew_path.front(); renew_path.pop_front();
Search(*nowdir, tem, find, last);
nowdir=find.dir_ptr;
nowdir->size-=size;
}
}
}
}
//分析路径字符串
void AnalysePath(string &filepath, queue<string> &path, deque<string> &renew_path) {
//清空地址队列
while (!path.empty()) path.pop();
while (!renew_path.empty()) renew_path.pop_front();
//开始解析路径字符串
string::iterator it=filepath.begin();
string tem="";
while (it!=filepath.end()) {
if (*it=='/') {
if (tem=="") it++;
else {path.push(tem); renew_path.push_back(tem); tem=""; it++;}
}
else {tem+=*it; it++;}
}
if (tem!="") {path.push(tem); renew_path.push_back(tem);}
}
//启动程序
int main() {
//初始化基本变量
int n; cin>>n;
string order, filepath;
long long filesize, ld, lr;
queue<string> path;
deque<string> renew_path;
//初始化一个根文件
dir root; root.name="ROOT";
//执行每一条命令
for (int i=0; i<n; i++) {
int flag=1;
//输入命令
cin>>order;
//校验命令码
if (order=="C") {
cin>>filepath>>filesize;
long long formersize=0;
AnalysePath(filepath, path, renew_path);
Creat(path, root, filesize, formersize, flag);
if (flag) {
RenewSize(renew_path, &root, filesize, formersize, order, flag);
cout<<'Y'<<endl;
}
else cout<<'N'<<endl;
}
else if (order=="R") {
cin>>filepath;
AnalysePath(filepath, path, renew_path);
Remove(path, root, filesize, flag);
RenewSize(renew_path, &root, filesize, filesize, order, flag);
cout<<'Y'<<endl;
}
else { //order=="Q"
cin>>filepath>>ld>>lr;
AnalysePath(filepath, path, renew_path);
Qcommand(path, root, ld, lr, flag);
if (flag) cout<<'Y'<<endl;
else cout<<'N'<<endl;
}
}
return 0;
}