程序设计思维与实践 Week9 作业 (1/2/智能班)

A - 咕咕东的目录管理器

题面

咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!

初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root

目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。

现在咕咕东可以在命令行下执行以下表格中描述的命令:

命令 类型 实现 说明
MKDIR s 操作 在当前目录下创建一个子目录 ss 是一个字符串 创建成功输出 "OK";若当前目录下已有该子目录则输出 "ERR"
RM s 操作 在当前目录下删除子目录 ss 是一个字符串 删除成功输出 "OK";若当前目录下该子目录不存在则输出 "ERR"
CD s 操作 进入一个子目录 s,s是一个字符串(执行后,当前目录可能会改变) 进入成功输出 "OK";若当前目录下该子目录不存在则输出 "ERR" 特殊地,若 s 等于 ".." 则表示返回上级目录,同理,返回成功输出 “OK”,返回失败(当前目录已是根目录没有上级目录)则输出 “ERR”
SZ 询问 输出当前目录的大小 也即输出 1+当前目录的子目录数
LS 询问 输出多行表示当前目录的 "直接子目录" 名 若没有子目录,则输出 "EMPTY";若子目录数属于 [1,10] 则全部输出;若子目录数大于 10,则输出前 5 个,再输出一行 "…",输出后 5 个。
TREE 询问 输出多行表示以当前目录为根的子树的前序遍历结果 若没有后代目录,则输出 "EMPTY";若后代目录数+1(当前目录)属于 [1,10] 则全部输出;若后代目录数+1(当前目录)大于 10,则输出前 5 个,再输出一行 "…",输出后 5 个。若目录结构如上图,当前目录为 "root" 执行结果如下,img
UNDO 特殊 撤销操作 撤销最近一个 "成功执行" 的操作(即MKDIR或RM或CD)的影响,撤销成功输出 "OK" 失败或者没有操作用于撤销则输出 "ERR"

输入

输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);

每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);

每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);

面对数据范围你要思考的是他们代表的 "命令" 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。

输出

每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。

时空限制

Time limit 6000 ms

Memory limit 1048576 kB

样例输入

1
22
MKDIR dira
CD dirb
CD dira
MKDIR a
MKDIR b
MKDIR c
CD ..
MKDIR dirb
CD dirb
MKDIR x
CD ..
MKDIR dirc
CD dirc
MKDIR y
CD ..
SZ
LS
TREE
RM dira
TREE
UNDO
TREE

样例输出

OK
ERR
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
9
dira
dirb
dirc
root
dira
a
b
c
dirb
x
dirc
y
OK
root
dirb
x
dirc
y
OK
root
dira
a
b
c
dirb
x
dirc
y

思路

按照学长的PPT来。。。

Step0. 无从下手
经验:如果一下子不习惯/无法从整个大局进行考虑,自顶向下地设计整个程序框架,不妨就从程序的入口开始。先写好程序入口,写好大致需要的变量。

Step1. 思考封装
考虑“有什么”和“做什么”。
比如刚刚我们有一个命令字符串“tmps”,它即代表一条命令。
考虑到一条命令不单有命令形式,还有命令参数,比如“MKDIR s”,等会我们肯定还要进行参数的分离,同类信息最好内聚,所以 —— 封装之!

Step2. 树形结构
维护一棵目录树是实现这道题的必然需求 —— 如何维护一棵树?
题目要求一个目录能够根据子目录的名字取到它的子目录
根据键取值, Key-Value ? 所以是 map ?
对,如果不要求字典序,用底层是哈希的 unordered_map, O(1) 的复杂度,将绝杀,可惜用不得
所以要用 map<string,目录>,它可以根据 key 也就是 string 在内部进行排序。这样每次可以 log 级别复杂度取到子目录

Step3. 解题框架
要开始写具体实现的时候,你发现,对于每条指令要有一个执行对象,那就是当前目录 “now”
如此,解题框架就如下:
即我们在 Directory 结构体里面写各种具体的实现
同时你发现两个问题,
第一,你要设计“返回值”告知某条命令的执行的结果成功/失败
第二,你想起来“UNDO”没法封装在某一个 Directory 内部,它是隶属于当前测试数据环境的
所以,对于执行成功的命令,你还要存起来,以备“UNDO”
回去题面分析一下可以 UNDO 的命令有关键
必须是 MKDIR、 RM、 CD 三种之一
必须已执行成功
如果 UNDO
MKDIR 的 UNDO 就是删掉,那么删谁?
RM 的 UNDO 就是加回去,那么加谁?
CD 的 UNDO 就是退回去,退到哪?
—— 需要保存每条指令执行的执行结果,保存到 -> struct Command{…}

Step5. 注入灵魂
直到要实现 TREE 命令的时候,你发现它是最难的
因为在后代节点数量大于 10 时,要开始前序遍历和后序遍历
并且对复杂度有要求!
TREE 的命令条数最多约为 1e5,此时整个树最多 5000 个节点
20 * 1e5 * 5000,你发现可能会 TLE
—— 你需要一个较好的实现
答案是缓存(懒更新),节点数远少于 TREE 操作数,指不定还有重复询
问,对于目录相同期间问过的相同问题,理应只有一次是计算过程

总结

补完前话中没有说完的部分
题意分析之利用时限计算复杂度设计数据结构:
设计树形数据结构时比起 O(n) 用 map 可以 O(logn) 来找子目录;
分析时限,发现 TREE 操作暴力实现是不可接受的,于是我们使用了记忆化(缓存)技巧。
解题框架设计之从何入手:
如果不知从何入手,就从 int main() 开始先写几行;
然后对于一个 “功能” 不要一上来就写细节,而是假装已经封装写好了,从而先避免细节,来关注整体设计。
面向对象中之封装不要过于拘束:
printf 究竟在类内还是类外,我们选择了都要
类似的抉择还会有的,坚持高内聚的架构,还是肆意放飞,你写代码舒服就好的。毕竟现在只是为了解决一道题,以后工程架构时这些经历会帮助你的。 (只要坚持不妥协)

代码

#include<iostream>
#include<map>
#include<vector>
#include<string.h>
using namespace std;

struct Directory {
	string name;
	map<string,Directory*> children;
	Directory* parent;
	bool updated;
	int subtreeSize;
	vector<string> *tenDes;
	Directory(string tname, Directory* tparent) {
		this->name = tname;
		this->parent = tparent;
		this->subtreeSize = 1;
		tenDes = new vector<string>; 
	}
	Directory* getChild(string name) {
		map<string,Directory*>::iterator it = children.find(name);
		if(it == children.end()) {
			return NULL;
		}
		return it->second;
	}
	Directory* mkdir(string name) {
		if(children.find(name)!=children.end()) {
			return NULL;
		}
		Directory* ch = new Directory(name,this);
		children[name] = ch;
		maintain(+1);
		return ch;
	}
	Directory* rm(string name) {
		map<string,Directory*>::iterator it = children.find(name);
		if(it == children.end()) {
			return NULL;
		}
		maintain(-1*it->second->subtreeSize);
		children.erase(it);
		return it->second;
	}
	Directory* cd(string name) {
		if(name == "..") {
			return this->parent;
		}
		return getChild(name);
	}

	bool addChild(Directory* ch) {
		if(children.find(ch->name)!=children.end()) {
			return false;
		}
		children[ch->name] = ch;
		maintain(+ch->subtreeSize);
		return true;
	}
	void maintain(int delta) {
		updated = true;
		subtreeSize += delta;
		if(parent!=NULL) {
			parent->maintain(delta);
		}
	}
	void sz() {
		printf("%d\n",this->subtreeSize);
	}
	void ls() {
		int sz = children.size();
		if(sz==0) printf("EMPTY\n");
		else if(sz <= 10) {
			map<string,Directory*>::iterator it = children.begin();
			for(int i=0; i<sz; i++,it++) {
				printf("%s\n",it->first.c_str());
			}
		} else {
			map<string,Directory*>::iterator it = children.begin();
			for(int i=0; i<5; i++,it++) {
				printf("%s\n",it->first.c_str());
			}
			printf("...\n");
			it = children.end();
			for(int i=0; i<5; i++) it--;
			for(int i=0; i<5; i++,it++) {
				printf("%s\n",it->first.c_str());
			}
		}
	}

	void treeAll(vector<string>* bar) {
		bar->push_back(name);
		map<string,Directory*>::iterator it = children.begin();
		for(; it!=children.end(); it++) {
			it->second->treeAll(bar);
		}
	}

	void treeLastSome(int num, vector<string>* bar) {
		int n = children.size();
		map<string,Directory*>::iterator it = children.end();
		while(n--) {
			it--;
			int sts = it->second->subtreeSize;
			if(sts>=num) {
				it->second->treeLastSome(num,bar);
				return;
			} else {
				it->second->treeLastSome(sts,bar);
				num -= sts;
			}
		}
		bar->push_back(name);
	}

	void treeFirstSome(int num, vector<string>* bar) {
		bar->push_back(name);
		if(--num == 0) return;

		int n = children.size();
		map<string,Directory*>::iterator it = children.begin();
		while(n--) {
			int sts = it->second->subtreeSize;
			if(sts>=num) {
				it->second->treeFirstSome(num,bar);
				return;
			} else {
				it->second->treeLastSome(sts,bar);
				num -= sts;
			}
			it++;
		}
	}

	void tree() {
		if(subtreeSize == 1) printf("EMPTY\n");
		else if(subtreeSize <= 10) {
			if(this->updated) {
				tenDes->clear();
				treeAll(tenDes);
				this->updated = false;
			}
			for(int i=0; i<subtreeSize; i++) printf("%s\n",tenDes->at(i).c_str());


		} else {
			if(this->updated) {
				tenDes->clear();
				treeFirstSome(5,tenDes);
				treeLastSome(5,tenDes);
				this->updated = false;
			}
			for(int i=0; i<5; i++) {
				printf("%s\n");
			}
		}
	}
};

string cmdnames[14] = {"mkdir","rm","cd","sz","ls","tree","undo","MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
struct command {
	int type;
	string arg;
	Directory* tmpDir;
	command(string s) {
		int i = 0;
		for(; i<14; i++) {
			if(cmdnames[i]==s) {
				type = i%7;
				if(type<3) cin>>arg;
			}
		}
	}
	
};

void solve() {
	string tmp;
	int n;
	cin>>n;
	Directory* now = new Directory("root",NULL);
	vector<command*> cmdList;
	while(n) {
		n--;
		cin>>tmp;
		command* cmd = new command(tmp);
		switch(cmd->type) {
			case 0:
				cmd->tmpDir = now->mkdir(cmd->arg);
				if(cmd->tmpDir == NULL) printf("ERR\n");
				else {
					printf("OK\n");
					cmdList.push_back(cmd);
				}
				break;
			case 1:
				cmd->tmpDir = now->rm(cmd->arg);
				if(cmd->tmpDir == NULL) printf("ERR\n");
				else {
					printf("OK\n");
					cmdList.push_back(cmd);
				}
				break;
			case 2: {
				Directory* ch = now->cd(cmd->arg);
				if(ch == NULL) printf("ERR\n");
				else {
					printf("OK\n");
					cmd->tmpDir = now;
					now = ch;
					cmdList.push_back(cmd);
				}
				break;
			}
			case 3:
				now->sz();
				break;
			case 4:
				now->ls();
				break;
			case 5:
				now->tree();
				break;
			case 6: {
				bool success = false;
				while(!success && !cmdList.empty()) {
					cmd = cmdList.back();
					cmdList.pop_back();
					switch(cmd->type) {
						case 0:
							success = (now->rm(cmd->arg)!=NULL);
							break;
						case 1:
							success = (now->addChild(cmd->tmpDir));
							break;
						case 2:
							now = cmd->tmpDir;
							success = true;
							break;
					}
				}
				printf(success?"OK\n":"ERR\n");
				break;
			}

		}
	}
}
int main() {
//	freopen("ou1.txt","w",stdout);
	int t;
	cin>>t;
	while(t){
		t--;
		solve();
	}
	return 0;
}

题面

最近,东东沉迷于打牌。所以他找到 HRZ、ZJM 等人和他一起打牌。由于人数众多,东东稍微修改了亿下游戏规则:

  • 所有扑克牌只按数字来算大小,忽略花色。
  • 每张扑克牌的大小由一个值表示。A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K 分别指代 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13。
  • 每个玩家抽得 5 张扑克牌,组成一手牌!(每种扑克牌的张数是无限的,你不用担心,东东家里有无数副扑克牌)

理所当然地,一手牌是有不同类型,并且有大小之分的。

举个栗子,现在东东的 "一手牌"(记为 α),瑞神的 "一手牌"(记为 β),要么 α > β,要么 α < β,要么 α = β。

那么这两个 "一手牌",如何进行比较大小呢?首先对于不同类型的一手牌,其值的大小即下面的标号;对于同类型的一手牌,根据组成这手牌的 5 张牌不同,其值不同。下面依次列举了这手牌的形成规则:

  1. 大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 5 张牌的大小总和。
  2. 对子:5 张牌中有 2 张牌的值相等。如果 α 和 β 都是对子,比较这个 "对子" 的大小,如果 α 和 β 的 "对子" 大小相等,那么比较剩下 3 张牌的总和。
  3. 两对:5 张牌中有两个不同的对子。如果 α 和 β 都是两对,先比较双方较大的那个对子,如果相等,再比较双方较小的那个对子,如果还相等,只能比较 5 张牌中的最后那张牌组不成对子的牌。
  4. 三个:5 张牌中有 3 张牌的值相等。如果 α 和 β 都是 "三个",比较这个 "三个" 的大小,如果 α 和 β 的 "三个" 大小相等,那么比较剩下 2 张牌的总和。
  5. 三带二:5 张牌中有 3 张牌的值相等,另外 2 张牌值也相等。如果 α 和 β 都是 "三带二",先比较它们的 "三个" 的大小,如果相等,再比较 "对子" 的大小。
  6. 炸弹:5 张牌中有 4 张牌的值相等。如果 α 和 β 都是 "炸弹",比较 "炸弹" 的大小,如果相等,比较剩下那张牌的大小。
  7. 顺子:5 张牌中形成 x, x+1, x+2, x+3, x+4。如果 α 和 β 都是 "顺子",直接比较两个顺子的最大值。
  8. 龙顺:5 张牌分别为 10、J、Q、K、A。

作为一个称职的魔法师,东东得知了全场人手里 5 张牌的情况。他现在要输出一个排行榜。排行榜按照选手们的 "一手牌" 大小进行排序,如果两个选手的牌相等,那么人名字典序小的排在前面。

不料,此时一束宇宙射线扫过,为了躲避宇宙射线,东东慌乱中清空了他脑中的 Cache。请你告诉东东,全场人的排名

输入

输入包含多组数据。每组输入开头一个整数 n (1 <= n <= 1e5),表明全场共多少人。
随后是 n 行,每行一个字符串 s1 和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。

输出

对于每组测试数据,输出 n 行,即这次全场人的排名。

样例输入

3
DongDong AAA109
ZJM 678910
Hrz 678910

样例输出

Hrz
ZJM
DongDong

思路

这道题主要是对于扑克牌大小的确定,所以只需要对哪些情况进行判定即可,将扑克牌做成一个结构体,对各种情况进行判定即可。

struct poke {
	int p[5];
	string name;
	map<int,int> cnt;
	set<int> st;
	int num;}

但是,我们可以注意到,在每一个-判断中会有判断子条件,所以我们可以增加判断条件,对于上一个条件相等的我们可以判断下一个条件依次进行:

int first,second,third,forth;

结构体代码如下:

struct poke {
	int p[5];
	string name;
	map<int,int> cnt;
	set<int> st;
	int num;
	int first,second,third,forth;
	void init(string n,string s) {
		first=0;
		second=0;
		third=0;
		forth = 0;
		cnt.clear();
		st.clear();
		num = 0;
		this->name = n;
		int length = s.length();
//		cout<<length<<endl;
		int ci = 0;
		for(int i=0; i<length; i++) {
			char tmp = s.c_str()[i];
			if(tmp==65) {
				p[ci] = 1;
			} else if(tmp==74) {
				p[ci] = 11;
			} else if(tmp==75) {
				p[ci] = 13;
			} else if(tmp==81) {
				p[ci] = 12;
			} else if(tmp==49) {
				i++;
				p[ci] = 10;
			} else {
				p[ci] = tmp - 48;
			}
			if(cnt.count(p[ci])!=0) cnt[p[ci]]++;
			else {
				cnt[p[ci]] = 1;
			}
			st.insert(p[ci]);
			ci++;
		}
		sort(p+0,p+5);
		if(p[0]==1&&p[1]==10&&p[2]==11&&p[3]==12&&p[4]==13) {
			num = 8;
		} else {
			int i = 0;
			for(i=0; i<4; i++) {
				if(p[i]+1!=p[i+1]) {
					break;
				}
			}
			if(st.size()==1) {
				second = p[0];
				num = 6;
			}

			else if(i==4) num = 7;
			else {
				if(st.size()==2) {
					set<int>::iterator it = st.begin();
					for(; it!=st.end(); it++) {
						if(cnt[*it]==4) break;
					}
					if(it!=st.end()) {
						second = *it;
						num = 6;
					} else {
						num = 5;
					}
				} else if(st.size()==3) {
					set<int>::iterator it = st.begin();
					for(; it!=st.end(); it++) {
						if(cnt[*it]==3) break;
					}
					if(it!=st.end()) num = 4;
					else {
						num = 3;
					}
				} else if(st.size()==4) num = 2;
				else {
					num = 1;
				}
			}
		}

	}
	void out() {
		cout<<name<<":";
		for(int i=0; i<5; i++) {
			cout<<p[i]<<" ";
		}
		cout<<endl;
		cout<<num<<endl;
	}
	void update() {
		first = num;
		if(num==8) {
			second = 0;
			third = 0;
		} else if(num == 7) {
			second = p[4];
			third = 0;
		} else if(num == 6) {
			set<int>::iterator it = st.begin();
			for(; it!=st.end(); it++) {
				if(cnt[*it]==1){
					third = *it;
					break;
				} 
			}

		} else if(num == 5) {
			set<int>::iterator it = st.begin();
			for(; it!=st.end(); it++) {
				if(cnt[*it]==3) second = *it;
				if(cnt[*it]==2) third = *it;
			}
		} else if(num==4) {
			set<int>::iterator it = st.begin();
			third = 0;
			for(; it!=st.end(); it++) {
				if(cnt[*it]==3) second = *it;
				if(cnt[*it]==1) third += (*it);
			}
		} else if(num==3) {
			set<int>::iterator it = st.begin();
			second = 0;
			third = 20;
			for(; it!=st.end(); it++) {
				if(cnt[*it]==2) {
					second = max(second,*it);
					third += min(third,*it);
				} else {
					forth = *it;
				}
			}
		} else if(num==2) {
			set<int>::iterator it = st.begin();
			second = 0;
			for(; it!=st.end(); it++) {
				if(cnt[*it]==2) {
					second = *it;
				} else {
					third += (*it);
				}
			}
		} else {
			for(int i=0; i<5; i++) {
				second += (p[i]);
			}
		}
	}
	bool operator < (poke b) const {
		if(first!=b.first) return first<b.first;
		else {
			if(second!=b.second)  return second<b.second;
			else {
				if(third!=b.third)  return third<b.third;
				else {
					if(forth!=b.forth)  return forth<b.forth;
					else {
						return name>b.name;
					}
				}
			}
		}
	}

};

C - 签到题,独立思考哈

SDUQD 旁边的滨海公园有 x 条长凳。第 i 个长凳上坐着 a_i 个人。这时候又有 y 个人将来到公园,他们将选择坐在某些公园中的长凳上,那么当这 y 个人坐下后,记k = 所有椅子上的人数的最大值,那么k可能的最大值mx和最小值mn分别是多少。

Input

第一行包含一个整数 x (1 <= x <= 100) 表示公园中长椅的数目
第二行包含一个整数 y (1 <= y <= 1000) 表示有 y 个人来到公园
接下来 x 个整数 a_i (1<=a_i<=100),表示初始时公园长椅上坐着的人数

Output

输出 mn 和 mx

Input Example

3
7
1
6
1

Output Example

6 13

样例解释

最初三张椅子的人数分别为 1 6 1
接下来来了7个人。
可能出现的情况为{1 6 8},{1,7,7},…,{8,6,1}
相对应的k分别为8,7,…,8
其中,状态{1,13,1}的k = 13,为mx
状态{4,6,5}和状态{5,6,4}的k = 6,为mn

思路

可能的最大值就是原先的最大值加上来的人。最小值则是向最大值对齐得到的最大值即为最小k。

代码

#include<iostream>
#include<cmath>
using namespace std;
int a[2000];
int main(){
	int x,y;
	cin>>x>>y;
	int m = 0;
	for(int i=0;i<x;i++){
		scanf("%d",&a[i]);
		m = max(m,a[i]);
	}
	int ans2 = m + y;
	int s = 0;
	for(int i=0;i<x;i++){
		s += (m-a[i]);
	}
	int ans1 = 0;
	if(y<=s) ans1 = m;
	else{
		ans1 = m + ceil((y-s)*1.0/x);
	}
	printf("%d %d",ans1,ans2);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/mopa/p/12934042.html