CSP 202012-3: 带配额的文件系统。100 分AC, 虽然是事后(皮)

本文提供了一种解决CSP202012-3带配额文件系统的算法实现,该算法通过预计算和递归方式避免了超时问题,实现了高效处理大规模数据的功能。

CSP 202012-3: 带配额的文件系统。100 分 AC,虽然是事后(皮)

#include<iostream>
#include<string>
#include<vector>
#include<map> 

using namespace std;

// 不进行提前记录,只能通过 70% 的测试用例,剩下的会超时(因为数据规模很大)
typedef struct treeNode {
	bool isFile; // true then file, false then dirctory
	long long LD, LR; // child limit  &  after children limit. Valid only isFile is false
	long long lr; // Valid only is a dir: 提前地、动态地、实时地记录后代配额,这样就不用进行庞大的递归计算了
	long long size; // Valid only is File is true
	map<string, struct treeNode*> children; // children's name: tree
} TreeNode, * Tree;

int main() {
	int n;
	long long calculateD(Tree);
	long long calculateR(Tree);
	bool create(Tree, string, long long);
	void remove(Tree, string);
	bool setQ(Tree, string, long long, long long);
	cin >> n;
	char* result = new char[n]();
	// initialize tree
	Tree tree = new TreeNode();
	// initialization done
	char command;
	string filePath; // directory's path or file's path
	string dirPath; // must be directory's path
	long long size;
	long long ld, lr;
	for (int time = 0; time < n; time++) {
		cin >> command;
		char success = 'Y';
		switch (command) {
		case 'C':
			cin >> filePath >> size;
			if (!create(tree, filePath, size))
				success = 'N';
			break;
		case 'R':
			cin >> filePath;
			remove(tree, filePath);
			break;
		case 'Q':
			cin >> dirPath >> ld >> lr;
			if (!setQ(tree, dirPath, ld, lr))
				success = 'N';
		}

		result[time] = success; // or 'N'
	}

	for (int i = 0; i < n; i++)
		cout << result[i] << endl;

	return 0;
}

vector<string> split(string filePath) { // create a wheel by myself, split a string to strs
	vector<string> vs;
	int start, end;
	int len = filePath.size();
	string s;
	start = 1;
	for (end = 1; end < len; end++) {
		// mainly use string object's assign(src_str, start_i, length) function
		if (filePath[end] == '/') {
			s.assign(filePath, start, end - start);
			vs.push_back(s);
			start = end + 1;
		}
	}
	if (len != 1) {
		s.assign(filePath, start, start - end);
		vs.push_back(s);
	}

	return vs;
}

void remove(Tree T, string filePath) {
	Tree tree = T;
	vector<string> vs = split(filePath);
	int len = vs.size();
	map<string, Tree>::iterator iter;
	int i;
	for (i = 0; i < len - 1; i++) {
		map<string, Tree>& children = tree->children;
		iter = children.find(vs[i]);
		if (iter == children.end())
			return;
		else
			tree = iter->second;
	}
	if (tree->isFile) return;
	map<string, Tree>& children = tree->children;
	iter = children.find(vs[i]);
	if (iter == children.end())
		return;
	

	// 现在确定指令正确,必然要删文件,tree 指向了待删结点的父结点。
	// for function create version 3.0, 删除目录之前,要先使用被删结点的信息 lr or size,
	// 以更新将要被删的文件路径上所有的目录的 lr
	long long minus;
	if (iter->second->isFile) minus = iter->second->size;
	else minus = iter->second->lr; 

	T->lr -= minus;
	for (i = 0; i < len - 1; i++) {
		map<string, Tree>& children = T->children;
		T = children[vs[i]];
		T->lr -= minus;
	}

	// 更新完成,可以删除了
	map<string, Tree>& c = tree->children;
	c.erase(vs[len-1]);
}

long long calculateD(Tree tree) {
	map<string, Tree>& children = tree->children;
	map<string, Tree>::iterator iter = children.begin();
	long long total = 0;
	for (; iter != children.end(); iter++)
		if (iter->second->isFile) total += iter->second->size;
	return total;
}

long long calculateR(Tree tree) {
	long long total = 0;
	map<string, Tree>& children = tree->children;
	map<string, Tree>::iterator iter = children.begin();
	for (; iter != children.end(); iter++) {
		if (iter->second->isFile) total += iter->second->size;
		else total += calculateR((Tree)iter->second);
	}
	return total;
}

// previously record every directory's lr, which is the sum of all the files' sizes,
// avoiding recursively calculating.
bool create(Tree T, string filePath, long long size) {
	Tree tree = T;

	/* Step 1: 走到已存在的最后一个目录 */
	vector<string> vs = split(filePath);
	int len = vs.size();
	int i;
	map<string, Tree>::iterator iter;
	for (i = 0; i < len - 1; i++) {
		map<string, Tree>& children = tree->children;
		iter = children.find(vs[i]);
		if (iter == children.end())  // 不存在这个目录,就 break 出来创建后面所有的目录
			break;
		tree = iter->second;
		if (tree->isFile) return false; // 有重名普通文件,执行失败
		// else it is a directory
	}

	/* Step 2: 检测前面存在的目录是否超出 LR 配额 */
	long long delta;
	if (i < len - 1) { // 提前跳出 for,表示目录不存在,要进行创建
		// 此时,delta 等于 size
		delta = size;
	}
	else { // i == len - 1, 路径上所有目录都存在,看是否有此文件
		map<string, Tree>& children = tree->children;
		iter = children.find(vs[i]);
		if (iter != children.end()) { // 有此文件,看是否是普通文件
			// delta 等于 new size - old size
			if (iter->second->isFile)  // 是普通文件,看是否 满足 tree 的 LD
				delta = size - iter->second->size;
			else return false; // 是个重名目录,执行失败
		}
		else  // 无此文件,要进行创建,delta 等于 size
			delta = size;

		if (tree->LD > 0 && calculateD(tree) + delta > tree->LD)
			return false;
	}
	// 至此,可以使用 delta 来检测路径上已存在目录的配额是否超出了
	int j;
	tree = T;
	if (tree->LR > 0 && tree->lr + delta > tree->LR)
		return false;
	for (j = 0; j < i; j++) { // i 是第一个没有找到的目录,or 路径终点的普通文件
		map<string, Tree>& children = tree->children;
		tree = children[vs[j]];
		if (tree->LR > 0 && tree->lr + delta > tree->LR)
			return false;
	}
	// 至此,所有条件都满足,能执行成功
	// 可以进行路径上未存在的目录和文件的创建了

	/* Step 3: 创建后面所有不存在的目录 */ 
	for (; i < len - 1; i++) {
		map<string, Tree>& children = tree->children;
		children[vs[i]] = new TreeNode();
		tree = children[vs[i]];
	}
	// 下面这种写法,可以统一 3 种情况:目录和文件都存在、目录存在文件不存在、目录和文件都不存在
	map<string, Tree>& children = tree->children;
	iter = children.find(vs[i]);
	
	if (iter == children.end()) { // 新建文件
		children[vs[i]] = new TreeNode();
		tree = children[vs[i]];
		tree->isFile = true;
		tree->size = size;
	}
	else { // 已存在, 则修改文件
		tree = iter->second;
		tree->size = size;
	}

	/* Step 4: 因为成功创建了文件,所以路径上所有目录的 lr 都加上一个 delta */
	T->lr += delta;
	for (i = 0; i < len - 1; i++) {
		map<string, Tree>& children = T->children;
		T = children[vs[i]];
		T -> lr += delta;
	}

	return true;
}

bool setQ(Tree tree, string dirPath, long long ld, long long lr) {
	vector<string> vs = split(dirPath);
	int len = vs.size();
	map<string, Tree>::iterator iter;
	for (int i = 0; i < len; i++) {
		map<string, Tree>& children = tree->children;
		iter = children.find(vs[i]);
		if (iter == children.end()) // not exists
			return false;
		tree = iter->second;
	}
	if (tree->isFile) return false; // not directory

	if (ld > 0 && calculateD(tree) > ld
		|| lr > 0 && tree->lr > lr)
		return false;
	// else
	tree->LD = ld;
	tree->LR = lr;

	return true;
}

<think>好的,我现在需要解决用户关于CSP 202012-T3配额文件系统的C++实现和解题思路的问题。首先,我得回忆一下这个题目的具体要求。题目涉及到文件系统配额管理,可能包括目录和文件的创建,以及配额的限制,比如子目录的数量、文件大小等。用户可能需要处理树形结构的数据,因此如何构建这个树结构是关键。 接下来,我得考虑如何表示文件系统的节点。每个节点可能是一个目录或文件,目录可以有子节点,并且需要记录配额信息,比如最大子目录数、最大文件大小等。这里可能需要一个结构体或类来表示节点,包含子节点的映射、当前的文件大小、配额限制等属性。 然后,处理用户的命令,比如创建文件或目录。创建时需要检查路径是否正确,是否已经存在同名节点,同时需要逐层检查父目录的配额是否允许新增。例如,创建文件时,要更新所有父目录的文件大小,并确保不超过各自的配额。这里需要注意路径解析,可能需要将路径割成各个组成部,逐层遍历树结构。 配额检查是关键部。每次添加文件或目录时,必须从当前目录向上遍历到根目录,检查每个层级的配额是否满足。比如,目录的子节点数量是否超过配额,或者文件总大小是否超出限制。如果任何一层配额不满足,整个操作应该失败。 还要考虑如何高效地处理这些检查。因为题目中的数据量可能较大,需要避免重复计算。例如,每个目录维护当前的文件大小和子目录数量,这样在检查时可以直接获取,而不需要每次遍历子树计算。 可能出现的问题包括路径解析错误,比如处理多个斜杠或者空路径部。此外,配额更新的同步问题,比如在创建文件时,需要先预检查配额是否足够,然后再实际创建,避免中间状态导致的不一致。 参考用户提供的引用[^2],用户提到学习建树思路的重要性,可能需要参考已有的树结构实现,比如使用哈希表来存储子节点,便于快速查找。同时,在处理配额时,每个节点需要记录自己的配额限制和当前使用情况。 现在,我需要将这些思路整理成步骤,并考虑如何用C++实现。例如,定义Node结构体,包含子节点映射、当前文件大小、配额等。然后处理每个命令,解析路径,逐层创建目录或文件,同时更新配额。最后,处理输出结果,成功或失败的原因。 可能还需要处理特殊情况,比如根目录的处理,路径以斜杠开头,或者路径中包含多个连续的斜杠。此外,配额的类型,比如目录配额和文件大小配额,需要别处理。 在测试时,需要注意大数据量的情况,确保时间效率。例如,使用哈希表(unordered_map)来存储子节点,以便快速查找和插入。同时,避免在每次操作时都遍历整个树,而是通过维护当前的使用情况来即时判断。 总结下来,解题步骤大致如下: 1. 定义树节点的结构,包括子节点、配额信息、当前使用情况。 2. 解析输入命令,离路径和参数(如文件大小)。 3. 处理路径,逐层遍历或创建目录节点。 4. 在创建文件或目录时,检查各级父目录的配额。 5. 根据检查结果决定操作是否成功,并输出相应信息。 在代码实现中,需要注意路径割、树结构的构建、配额的动态更新以及错误处理。同时,参考已有的AC代码可以帮助避免常见的错误,比如配额更新的顺序或者路径解析的细节问题。</think>针对CSP 202012-T3配额文件系统题目,以下是解题思路与C++实现的关键步骤: --- ### 一、问题析 题目要求模拟一个支持**目录配额**和**后代配额**的文件系统,需处理以下操作: 1. **创建文件**:检查路径合法性及各级目录的配额限制 2. **创建目录**:需处理目录已存在的情况 3. **配额限制**: - **目录配额**:限制直接子目录/文件数量 - **后代配额**:限制该目录下所有文件总大小 --- ### 二、数据结构设计 使用**树形结构**表示文件系统,每个节点包含: ```cpp struct Node { bool isDir; // 是否为目录 unordered_map<string, Node*> children; // 子节点 // 配额相关 int dirLimit = -1; // 目录配额(最大子节点数) int sizeLimit = -1; // 后代配额(最大总文件大小) int currentSize = 0; // 当前总文件大小 }; ``` --- ### 三、核心算法步骤 #### 1. 路径解析 将路径按`/`割为组件列表: ```cpp vector<string> parsePath(const string& path) { vector<string> res; stringstream ss(path.substr(1)); // 去掉开头的&#39;/&#39; string item; while (getline(ss, item, &#39;/&#39;)) { if (!item.empty()) res.push_back(item); } return res; } ``` #### 2. 创建文件/目录 ```cpp bool createFile(const vector<string>& path, int fileSize) { Node* cur = root; // 遍历到倒数第二个节点(父目录) for (int i = 0; i < path.size()-1; ++i) { string& name = path[i]; if (!cur->children.count(name)) return false; // 路径不存在 cur = cur->children[name]; if (!cur->isDir) return false; // 非目录节点 } // 检查配额 if (!checkQuota(cur, path.back(), fileSize)) return false; // 创建文件并更新配额 cur->children[path.back()] = new Node{false}; updateSize(cur, fileSize); return true; } ``` #### 3. 配额检查 ```cpp bool checkQuota(Node* parent, const string& name, int fileSize) { // 检查目录配额 if (parent->dirLimit != -1 && parent->children.size() >= parent->dirLimit && !parent->children.count(name)) { return false; } // 检查后代配额 int total = parent->currentSize + fileSize; if (parent->sizeLimit != -1 && total > parent->sizeLimit) { return false; } // 回溯检查所有祖先 Node* cur = parent; while (cur) { if (cur->sizeLimit != -1 && (cur->currentSize + fileSize) > cur->sizeLimit) { return false; } cur = cur->parent; // 需要为节点添加parent指针 } return true; } ``` --- ### 四、实现要点 1. **树形遍历**:使用哈希表存储子节点,实现$O(1)$查找 2. **配额更新**:创建文件时,从当前目录向上回溯更新`currentSize` 3. **错误处理**: - 路径不存在时返回`"N"` - 配额超限时返回`"N"` - 成功时返回`"Y"` --- ### 五、性能优化 1. **预计算总大小**:每个节点维护`currentSize`,避免每次重新计算 2. **快速路径解析**:使用字符串流高效割路径 3. **哈希表选择**:使用`unordered_map`而非`map`,提升查找效率 --- ### 六、测试用例设计示例 ```text 输入: 10 C /A/B/file.txt 100 C /A/B # 创建目录 Q /A 2 150 # 设置配额(目录配额2,后代配额150) C /A/B/file2.txt 50 C /A/C/file3.txt 100 输出: Y # 创建文件成功 Y # 创建目录成功 Y # 设置配额成功 Y # 总大小100+50=150≤150 N # 超过后代配额(150+100=250>150) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值