算法思想
这道题是一个比较复杂的模拟题,难度不是很大,只要耐心地将每一种情况都考虑到,还是很容易100分的。想练习大模拟的朋友,可以耐下心来做一下这个题,这道题的细节还是很多的。
依据题目,可以主要有三种操作,分别为创建普通文件、移除文件和设置配额值这三个操作。通过对文件系统的了解,可以判断出采用的数据结构是树,我之前也写了一个关于树的数据结构的CSDN,链接如下:数据结构-树。本道题也基本上采用的是这样的数据结构。
代码详解
1、文件节点的数据结构
struct node {
int father; //父节点
map<string, int>child; //孩子节点
int type; //1:文件 2:目录
long long ld, lr; //目录配额、后代配额
long long size; //如果是普通文件的话,代表普通文件大小
long long ld_r, lr_r; //实际孩子文件大小总和、
//实际后代文件大小总和
}Node[4000010];
2、召回操作
在创建普通文件的操作中,扫描文件路径的时候,只有最后扫描到最后一层的普通文件时,才能判断出来是否满足创建普通文件的要求,如果满足创建普通文件的要求,就继续创建普通文件即可;如果不满足创建普通文件的要求的话,则需要将之前新创建的目录文件删去。因此,小编单独实现了这个召回操作,代码如下:
vector<pair<int, string> >reback;
void Reback()
{
int i;
for (i = 0; i < reback.size(); i++)
{
int a = reback[i].first;
string b = reback[i].second;
Node[a].child.erase(Node[a].child.find(b));
}
}
vector数据reback中存储的就是如果创建普通文件失败后需要删去的新创建的目录文件的名字(string类型)以及其父节点对应的节点号(int类型)。
3、主函数
为了使代码更加简洁,主函数通过调用三个函数executeC、executeR、executeQ实现对三个操作的调用,代码如下:
int main()
{
int i, j;
Node[0].type = 2; //初始化根节点
Node[0].ld = LLONG_MAX / 3; //设置最大配额
Node[0].lr = LLONG_MAX / 3;
Node[0].father = -1;
int op_num;
cin >> op_num; // 输入操作数
for (i = 1; i <= op_num; i++)
{
char op;
cin >> op; //输入操作类型
if (op == 'C')
{
cout << executeC() << endl; //执行创建普通文件操作
}
else if (op == 'R')
{
cout << executeR() << endl; //执行移除文件操作
}
else if (op == 'Q')
{
cout << executeQ() << endl; //执行设置配额值操作
}
}
return 0;
}
代码中,LLONG_MAX是long long类型数据的最大值,如果没有对配额进行设置,则统一设置成如此的最大值。
4、创建普通文件
(1)首先要进行的预处理要两个:第一个就是读入普通文件的路径以及普通文件的大小,而另一个就是找到普通文件路径中最后一个’/’,这个操作的意义就是找到目录文件与普通文件的分水岭,最后一个’/'之前的文件都应该是目录文件,而其后面只有一个文件,应该是普通文件的名字。
(2)对于前面的目录文件,我们要从根节点进行一层一层的扫描,如果该层不存在该名字的目录文件,则需要创建一个新的目录文件,同时将这个目录文件的名字以及其父节点的序号添加到召回操作的Vector数组reback中,如果创建普通文件失败,这些新添加的目录文件是要删除的;如果该层存在该名字的文件,那么要先判断一下这个名字的文件的类型:如果这个同名的文件是普通文件,则不满足创建普通文件的要求;如果这个同名的文件也是目录文件,则继续向下扫描即可。
(3)对于最后一个普通文件,提取出其名字之后,判断该层是否有同名的文件,如果这个同名的文件是目录文件,则无法满足创建条件,创建失败。如果没有同名文件或者同名文件也为普通文件,则判断新改变的文件大小是否满足该文件的所有父节点的配额要求,如果满足配额要求,则进行真正的创建;否则,按照创建失败处理。
具体的代码如下:
string executeC()
{
int i, j;
string filepath;
long long filesize;
cin >> filepath >> filesize; //输入普通文件路径以及普通文件大小
int last = -1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i; //找到最后一个/的位置,后面就是文件,前面就是目录
break;
}
}
int current = 1;
int id = 0;
reback.clear();
int oldnum = num;
//前面的目录操作
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/') //读入每一个目录文件的名字
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end()) //没有这个目录,那就创建一个咯
{
num++;
Node[id].child[t] = num;
Node[num].father = id;
Node[num].type = 2;
Node[num].ld = LLONG_MAX / 3;
Node[num].lr = LLONG_MAX / 3;
reback.push_back(make_pair(id, t));
id = num;
}
else //存在这个目录文件
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) //如果这个是文件,则直接输出'N'
{
num = oldnum;
Reback();
return "N";
}
id = childid;
}
}
//最后一个的文件操作
string t = "";
for (i = last + 1; i < filepath.length(); i++)
{
t = t + filepath[i];
}
if (Node[id].child.find(t) != Node[id].child.end()) //存在这个文件
{
int childid = Node[id].child[t];
if (Node[childid].type == 2) //如果这个是目录,直接输出'N'
{
num = oldnum;
Reback();
return "N";
}
}
//判断新的改变是否满足配额要求
long long changesize = 0;
if (Node[id].child.find(t) == Node[id].child.end()) changesize = filesize;
else changesize = -Node[Node[id].child[t]].size + filesize;
if (Node[id].ld_r + changesize > Node[id].ld)
{
num = oldnum;
Reback();
return "N";
}
int now = id;
while (now != -1)
{
if (Node[now].lr_r + changesize > Node[now].lr)
{
id = oldnum;
Reback();
return "N";
}
now = Node[now].father;
}
//如果满足配额要求,则执行创建普通文件操作以及修改变化的参数
if (Node[id].child.find(t) == Node[id].child.end())
{
num++;
Node[num].type = 1;
Node[num].father = id;
Node[num].size = filesize;
Node[id].child[t] = num;
}
else Node[Node[id].child[t]].size = filesize;
Node[id].ld_r = Node[id].ld_r + changesize;
now = id;
while (now != -1)
{
Node[now].lr_r = Node[now].lr_r + changesize;
now = Node[now].father;
}
return "Y";
}
5、移除文件
这一部分,实现的算法思想较创建普通文件的算法思想简单很多,但是大致的思想都是一样的,代码也有类似的地方,没有什么特殊的。前面的判断、实现基本上与第四个部分差不多,不一样的就是如果真的存在要删除的普通文件,需要将想要删除的普通文件的大小提取出来,对其所有的父节点的参数进行相应的更新。
具体代码如下:
string executeR()
{
int i;
string filepath;
cin >> filepath; //输入文件路径
int current = 1;
int id = 0;
int last = -1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i;
break;
}
}
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/')
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end()) //没有这个目录,直接按照删除成功处理
{
return "Y";
}
else //存在这个目录,继续扫描下去
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) return "Y";
id = childid;
}
}
string t = "";
for (i = last + 1; i < filepath.length(); i++) //提取最后想要删除的文件名
t = t + filepath[i];
if (Node[id].child.find(t) == Node[id].child.end())
return "Y";
long long delsize = 0;
int delNode = Node[id].child[t]; //删除文件的大小
if (Node[delNode].type == 1)
{
Node[id].ld_r = Node[id].ld_r - Node[delNode].size;
delsize = Node[delNode].size;
Node[id].child.erase(Node[id].child.find(t));
}
else if (Node[delNode].type == 2)
{
delsize = Node[delNode].lr_r;
Node[id].child.erase(Node[id].child.find(t));
}
int now = id;
while (now != -1)
{
Node[now].lr_r = Node[now].lr_r - delsize;
now = Node[now].father;
}
return "Y";
}
6、设置配额值
文件系统中,每一个节点都已经存储了该节点孩子文件以及后代文件的大小的总和,只需要按照文件路径找到这个节点,然后判断即可,如果:新设定的配额满足要求,则设置配额值成功,否则设置失败。
具体的代码如下:
string executeQ()
{
int i;
string filepath;
cin >> filepath;
long long ld, lr;
cin >> ld >> lr;
if (ld == 0) ld = LLONG_MAX / 3;
if (lr == 0) lr = LLONG_MAX / 3;
int id = 0;
int last = -1;
int current = 1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i;
break;
}
}
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/')
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end())
return "N";
else
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) return "N";
id = childid;
}
}
string t = "";
for (i = last + 1; i < filepath.length(); i++) t = t + filepath[i];
int Qnode = 0;
if (t == "") Qnode = 0;
else
{
if (Node[id].child.find(t) == Node[id].child.end()) return "N";
else Qnode = Node[id].child[t];
}
if (Node[Qnode].type == 1) return "N";
if (ld < Node[Qnode].ld_r || lr < Node[Qnode].lr_r) return "N";
Node[Qnode].ld = ld;
Node[Qnode].lr = lr;
return "Y";
}
7、感想
这道CSP真题真的比较恶心,比较耗时,不过真的有较高的练习价值,朋友们可以打卡这道题哟
8、完整代码
#include<iostream>
#include<cstdio>
#include<map>
#include<vector>
#include<cstring>
#pragma warning (disable:4996)
using namespace std;
int num = 0;
struct node {
int father; //父节点
map<string, int>child; //孩子节点
int type; //1:文件 2:目录
long long ld, lr;
long long size;
long long ld_r, lr_r;
}Node[4000010];
vector<pair<int, string> >reback;
void Reback()
{
int i;
for (i = 0; i < reback.size(); i++)
{
int a = reback[i].first;
string b = reback[i].second;
Node[a].child.erase(Node[a].child.find(b));
}
}
string executeC()
{
int i, j;
string filepath;
long long filesize;
cin >> filepath >> filesize; //输入普通文件路径以及普通文件大小
int last = -1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i; //找到最后一个/的位置,后面就是文件,前面就是目录
break;
}
}
int current = 1;
int id = 0;
reback.clear();
int oldnum = num;
//前面的目录操作
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/') //读入每一个目录文件的名字
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end()) //没有这个目录,那就创建一个咯
{
num++;
Node[id].child[t] = num;
Node[num].father = id;
Node[num].type = 2;
Node[num].ld = LLONG_MAX / 3;
Node[num].lr = LLONG_MAX / 3;
reback.push_back(make_pair(id, t));
id = num;
}
else //存在这个目录文件
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) //如果这个是文件,则直接输出'N'
{
num = oldnum;
Reback();
return "N";
}
id = childid;
}
}
//最后一个的文件操作
string t = "";
for (i = last + 1; i < filepath.length(); i++)
{
t = t + filepath[i];
}
if (Node[id].child.find(t) != Node[id].child.end()) //存在这个文件
{
int childid = Node[id].child[t];
if (Node[childid].type == 2) //如果这个是目录,直接输出'N'
{
num = oldnum;
Reback();
return "N";
}
}
//判断新的改变是否满足配额要求
long long changesize = 0;
if (Node[id].child.find(t) == Node[id].child.end()) changesize = filesize;
else changesize = -Node[Node[id].child[t]].size + filesize;
if (Node[id].ld_r + changesize > Node[id].ld)
{
num = oldnum;
Reback();
return "N";
}
int now = id;
while (now != -1)
{
if (Node[now].lr_r + changesize > Node[now].lr)
{
id = oldnum;
Reback();
return "N";
}
now = Node[now].father;
}
//如果满足配额要求,则执行创建普通文件操作以及修改变化的参数
if (Node[id].child.find(t) == Node[id].child.end())
{
num++;
Node[num].type = 1;
Node[num].father = id;
Node[num].size = filesize;
Node[id].child[t] = num;
}
else Node[Node[id].child[t]].size = filesize;
Node[id].ld_r = Node[id].ld_r + changesize;
now = id;
while (now != -1)
{
Node[now].lr_r = Node[now].lr_r + changesize;
now = Node[now].father;
}
return "Y";
}
string executeR()
{
int i;
string filepath;
cin >> filepath; //输入文件路径
int current = 1;
int id = 0;
int last = -1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i;
break;
}
}
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/')
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end()) //没有这个目录,直接按照删除成功处理
{
return "Y";
}
else //存在这个目录,继续扫描下去
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) return "Y";
id = childid;
}
}
string t = "";
for (i = last + 1; i < filepath.length(); i++) //提取最后想要删除的文件名
t = t + filepath[i];
if (Node[id].child.find(t) == Node[id].child.end())
return "Y";
long long delsize = 0;
int delNode = Node[id].child[t]; //删除文件的大小
if (Node[delNode].type == 1)
{
Node[id].ld_r = Node[id].ld_r - Node[delNode].size;
delsize = Node[delNode].size;
Node[id].child.erase(Node[id].child.find(t));
}
else if (Node[delNode].type == 2)
{
delsize = Node[delNode].lr_r;
Node[id].child.erase(Node[id].child.find(t));
}
int now = id;
while (now != -1)
{
Node[now].lr_r = Node[now].lr_r - delsize;
now = Node[now].father;
}
return "Y";
}
string executeQ()
{
int i;
string filepath;
cin >> filepath;
long long ld, lr;
cin >> ld >> lr;
if (ld == 0) ld = LLONG_MAX / 3;
if (lr == 0) lr = LLONG_MAX / 3;
int id = 0;
int last = -1;
int current = 1;
for (i = filepath.length() - 1; i >= 0; i--)
{
if (filepath[i] == '/')
{
last = i;
break;
}
}
while (current < last)
{
string t = "";
while (current < last && filepath[current] != '/')
{
t = t + filepath[current];
current++;
}
current++;
if (Node[id].child.find(t) == Node[id].child.end())
return "N";
else
{
int childid = Node[id].child[t];
if (Node[childid].type == 1) return "N";
id = childid;
}
}
string t = "";
for (i = last + 1; i < filepath.length(); i++) t = t + filepath[i];
int Qnode = 0;
if (t == "") Qnode = 0;
else
{
if (Node[id].child.find(t) == Node[id].child.end()) return "N";
else Qnode = Node[id].child[t];
}
if (Node[Qnode].type == 1) return "N";
if (ld < Node[Qnode].ld_r || lr < Node[Qnode].lr_r) return "N";
Node[Qnode].ld = ld;
Node[Qnode].lr = lr;
return "Y";
}
int main()
{
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
int i, j;
Node[0].type = 2;
Node[0].ld = LLONG_MAX / 3;
Node[0].lr = LLONG_MAX / 3;
Node[0].father = -1;
int op_num;
cin >> op_num; // 输入操作数
for (i = 1; i <= op_num; i++)
{
char op;
cin >> op; //输入操作类型
if (op == 'C')
{
cout << executeC() << endl; //执行创建普通文件操作
}
else if (op == 'R')
{
cout << executeR() << endl; //执行移除文件操作
}
else if (op == 'Q')
{
cout << executeQ() << endl; //执行设置配额值操作
}
}
return 0;
}