CCF 2020-12-3 ——满分实现
带配额的文件系统
思路
对于这种大模拟,我们需要非常注意细节;
- 结点设计 ,需要尽可能的简单;
- CCF的第三题几乎都是需要STL库的,否则很麻烦,尽可能把STL库的map、set、priority_queue、deqeue、stack、vector等等都掌握应用熟练;
- 本题显然需要树的结构,因此指针的应用也需要掌握熟练;
结点设计
struct Node{
//就是存储目录
map<string,Node*> nextD;
//文件直接用数字来保存即可
map<string,long long int> f;
//s1孩子的大小限制,s2后代限制
long long int s1,s2;
//r1当前的孩子文件大小、r2当前的后代文件大小
long long int r1,r2;
Node(){
s1 = 0;s2 = 0;
r1 = 0; r2 = 0;
f.clear();
nextD.clear();
}
};
Node *root = new Node();
main函数
int main(){
int n = 0;
scanf("%d",&n);
char x;
long long int size,LD,LR;
char a[105];
bool ans =0;
while(n--){
getchar(); //吃掉回车
scanf("%c",&x);
scanf("%s",a);
string s = a;
if(x == 'C')
{scanf("%lld",&size);ans = create(s,size);}
else if(x == 'Q')
{scanf("%lld%lld",&LD,&LR);ans = quilfy(s,LD,LR);}
else{
ans = remove(s);
}
if(ans == 0) printf("N\n");
else printf("Y\n");
}
return 0;
}
需要格外注意,所有数字不超过1018,那么int是不够的,需要long long int
使用scanf会快上不少,当然ccf对时间并不是特别限制,那么用cin也是可以的,但是如果使用scanf的话需要注意getchar吃掉回车
把三个功能分别写一个函数,然后循环调用即可
R函数
bool remove(string &s){
Node * p = root;
if(s.size() == 1&&s[0] == '/'){
p->f.clear();
p->nextD.clear();
p->r1 = 0;p->r2=0;p->s1=0;p->s2 =0;
return 1;
}
int i = 0,j = 0,n = s.size();
long long int siz = 0;
stack<Node*> lj;
lj.push(p);
while(i<n){
if(s[i]!='/'||(i!=0&&i==n-1&&s[i]=='/')) return 1;
j = i+1;
while(j!=n&&s[j]!='/'){j++;}
if(j == i+1) return 1;
string now = s.substr(i+1,j-i-1);
//判断是否是最后一个
if(j != n){
if(!p->nextD.count(now)) return 1;
p = p->nextD[now];
lj.push(p);
}
else{ //路径的最后一个文件或是目录删除
if(p->nextD.count(now)) {
Node*nex = p->nextD[now];
siz = nex->r2;
p->nextD.erase(now);
}
else if(p->f.count(now)){//文件
siz = p->f[now];
p->f.erase(now);
p->r1 -= siz;//剩下的后代的一起全部删去
}
else return 1;
}
i = j;
}//需要更新我们的r1、r2,如果删的是目录那么孩子的不用变,否则孩子的要变
while(!lj.empty()){
p = lj.top();lj.pop();
p->r2 -= siz;
}
return 1;
}
我是通过边做边分析路径的,如果不熟练可以先分析路径,把所有的文件名存到vector中
为了将我们删除的文件的大小同时反馈到向上的各级目录中,我在向下沿着路径到目标文件的过程中将各个目录的指针进行了保存,这样当我们得到删除文件的大小(siz),那么可以将存在于栈中的目录的r2全部减去该值;
注意
如果文件是普通文件,那么需要对父目录的r1(存孩子文件大小)同时减去siz
Q函数
bool quilfy(string &s,long long int &LD,long long int &LR){
Node * p = root;
int i = 0,j = 0,n = s.size();
if(s.size() == 1&&s[0] == '/'){
if((LD!=0&&p->r1>LD)||(LR!=0&&p->r2>LR))return 0;
p->s1 = LD; p->s2 = LR;
return 1;
}
while(i<n){
if(s[i]!='/'||(i!=0&&i==n-1&&s[i]=='/')) return 0;
j = i+1;
while(j!=n&&s[j]!='/'){j++;}
if(j == i+1) return 0;
string now = s.substr(i+1,j-i-1);
//判断是否是最后一个
if(j != n){
if(!p->nextD.count(now)) return 0;
p = p->nextD[now];
}
else{ //最后一个需要改配额
if(p->nextD.count(now)) {
p = p->nextD[now];
if((LD!=0&&p->r1>LD)||(LR!=0&&p->r2>LR))return 0;
p->s1 = LD;
p->s2 = LR;
}
else return 0;
}
i = j;
}
return 1;
}
Q函数是最简单的函数,只需要沿着路径向目录下走即可,如果走不通那么说明错误,直接返回0
在找到目录后,判断是否可以改,即限制s1、s2是否满足条件,满足则改
C函数
bool create(string &s,long long int &size){
Node * p = root;
int i = 0,j = 0,n = s.size();
vector<Node*> lj;
lj.push_back(p);
bool jc = false;
int ns = 0;
while(i<n){
if(s[i]!='/'||(i==n-1&&s[i]=='/')) return 0;
j = i+1;
while(j!=n&&s[j]!='/'){j++;}
if(j == i+1) return 0;
string now = s.substr(i+1,j-i-1);
//判断是否是最后一个
if(j != n){
if(!p->nextD.count(now)) { //不存在需要创建(也就是说此时我们的限制解除了)
//在更改前需要先判断前面的加size是否满足Q限制
if(!jc){
for(int o = 0;o<lj.size();o++){
if(lj[o]->s2!=0&&lj[o]->r2+size>lj[o]->s2) return 0;
} jc = true;}
if(p->f.count(now)) return 0;
p->nextD[now] = new Node();
p = p->nextD[now];
p->r2 = size;
}else{
p = p->nextD[now];
lj.push_back(p);
}
}
else{ //最后一个创建或者改变文件
if(p->nextD.count(now)) {return 0;} //目录冲突
else if(p->f.count(now)){//文件改变
//同理需要进行判断
ns = p->f[now];
if(!jc){
for(int o = 0;o<lj.size();o++){
if(lj[o]->s2!=0&&lj[o]->r2-ns+size>lj[o]->s2) return 0;
} jc = true;}
if((p->s1!=0&&p->r1-ns+size>p->s1)||(p->s2!=0&&p->r2-ns+size>p->s2)) return 0;
p->f[now] = size;
p->r1 += (size-ns);
}
else{ //文件创建
if(!jc){
for(int o = 0;o<lj.size();o++){
if(lj[o]->s2!=0&&lj[o]->r2+size>lj[o]->s2) return 0;
} jc = true;}
if((p->s1!=0&&p->r1+size>p->s1)||(p->s2!=0&&p->r2+size>p->s2)) return 0;
p->f[now] = size;
p->r1 += size;
}
}
i = j;
}//需要更新我们的r2
for(int o = 0;o<lj.size();o++){
lj[o]->r2 += (size-ns);
}
return 1;
}
C的函数是最为麻烦的,因为我们需要知道是更改文件还是添加文件,对于前者我们的限制的判断是需要对路径上的目录的 ** r2+size(更改后的值)-ns(原来的大小) **是否满足限制,
而添加文件则对路径上的文件的每一个r2+size是否满足限制,两者判断方式不同
解决方法
使用vector存储路径上的目录,当我们遇到需要创建的结点时,说明是第二种情况,那么我们通过第二种遍历一次目录,如果全部满足则说明可以添加结点,此时才能进行结点创建,否则什么都不做返回0
注意 这里的判断以及后面的判断都只需要进行一次,因为后面可能还会有结点需要创建,那么不用再进行多余的遍历
而对于第一种情况,我们可以在最后一个目录/文件处理的地方进行遍历
一定要在遍历一次目录路径列表看是否可以对我们的文件系统结构进行改变之后再改变
注意点
题目并不难,但是需要我们足够细心
- 尽可能使用自己熟悉的结构,在结点的定义中的map其实也可以换成set等
- 在敲代码之前先打一下草稿,最好写一下伪代码,多思考自己的思路会不会有问题
- 第三题不怎么看重算法,但是很考验字符串的熟练程度以及对复杂问题的简化能力
- 如果考试的时候找不出错误,那还是建议不要死磕,说不定第四第五题更简单