WEEK(9)作业——面对对象与模拟题(CSP-T3)

A-咕咕东的目录管理器

题目描述

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

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

目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
在这里插入图片描述
现在咕咕东可以在命令行下执行以下表格中描述的命令:
在这里插入图片描述
在这里插入图片描述

时空限制

Time limit 6000 ms

Memory limit 1048576 kB

Sample

Input

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

Output:

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

解题思路

  1. 准备工作
  • 将文件目录看成一棵树,对每个文件夹/文件看成树上的一个节点。建立节点的结构体Directory。
    包含节点名称(即文件夹名),子节点名称映射,父节点编号,子树大小sz(包括自己),pre,bck用于root输出,更新标签flag。
  • 对每个命令建立命令的结构体Command,包括命令名,命令类型,文件名(若命令类型为“操作”的话)。
  • 我们如何标识一个文件呢?——每个文件/文件夹有唯一的节点编号,建立Directory数组node,node[i]中存放每个节点的信息。
  1. 命令执行
  • now存贮当前目录编号,cnt表示总的文件编号
  • “MKDIR s”:在当前目录下新建子目录s,即增加子节点——用install_node先创建子节点,并将其加到当前节点的子节点映射mp中,同时更新子节点所有祖先直到root的sz
  • “RM s”:删除当前目录下的子目录s,涉及两个问题:1.在当前节点的子节点映射mp中删除该子节点 2.更新该子节点所有祖先直到root的sz
  • “CD s”:若cmd的str为“…”,直接返回父节点目录,now=node[now].par;否则,查找名称为cmd.str的子节点编号,更新now
  • “SZ” :对当前目录,输出节点的sz,在O(1)的复杂度下直接查询当前目录大小!
  • “LS”:输出“直接子目录名”,因此使用当前节点的子节点映射mp即可,这里要注意当其子节点数>10时,迭代器反向遍历的写法
  • “TREE”:如果节点个数大于10的时候,需要整个子树的前序遍历结果。而当每次都遍历一遍的话,树的最大规模5000,一次前序遍历计算量为5000,TREE命令最多有1e5-5000≈1e5条,显然会TLE!!!
    1.每个节点中的pre,bck向量存储他们的前序遍历结果(当后代目录数+1>10时,pre只存储前序遍历的前五个子节点名,bck只存储后五个,当<10时,bck与pre相同),每次遍历子代后节点flag置1
    2.flag标记该节点是否在上次遍历后被更新过(每次"MKDIR"和"RM"操作都会使经过的祖宗节点的flag置0),若未被更新过,那么下次需要输出时直接按要求输出即可,不需再次遍历对pre和bck更新
    3.这里注意到节点数远少于TREE的操作数,而且说不定还有重复询问,对目录相同期间询问过的相同问题,理应只进行一次遍历过程
  • “UNDO”:我们用了一个向量vector<pair<string,pair<int,int>>> v记录命令的执行次序,string表示命令,pair的first为该命令下的当前目录now,second为命令中所指明文件s(新建、删除或转到)对应节点的编号。pop出最近一次成功执行的命令,判断该命令的类型并更新数据,其中"MKDIR"用"RM"实现"UNDO",反之亦然,"CD"直接更改now反向行走即可。一定要注意成功执行的指令

注意

  • 多组数据注意初始化now,cnt,v和node[0]为root,root的父节点设置为-1
  • cmd为每次命令实例
  • "RM"操作只是删除两个节点间的边,而并非完全把子树删掉

总结

本题的关键点:

  1. 树形结构、动态创建、删除
  2. 字典序(map恰好在这道题中派上用场,且查找的复杂度为log(n)!)
  3. 撤销操作“UNDO”
  4. 输出数据与数量有关

对这道复杂的模拟题,要分别实现这七种命令。可以先分而治之,独立实现各个命令,好的封装可以理清思路。再寻求各个命令之间的联系,不要一上来就写细节,而要先避免细节,假装已经封装好,整体设计。同时要特别注意的是利用时限计算复杂度设计数据结构,例如此题的map和懒更新(记忆化)的技巧。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<vector>
#include<algorithm>

using namespace std;

const int maxn=1e5+100;
const string cmd_string[]={"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};

struct Command{
	string name,str;
	int type;
	
	void init(string s) {
		name=s;
		for(int i=0;i<7;++i) {
			if(s==cmd_string[i]) {
				type=i;
				if(i<3) cin>>str;
				break;
			}
		}
	}
}cmd;
//目录
struct Directory{
	//名称
	string name;
	//后代名称->编号映射
	map<string,int> mp;
	//父亲节点,子树规模
	int par,sz;
	vector<string> pre,bck;
	bool flag;//更新标签 
	
	void init(string s,int p) {
		flag=0;par=p;
		name=s;sz=1;
		pre.clear();
		bck.clear();
		mp.clear();
	} 
}node[maxn]; 

int cnt,now;//节点数,当前节点
vector<pair<string,pair<int,int>>> v;//存储顺序 

int T,Q;

void install_node(string s,int p) {
	node[++cnt].init(s,p);
	node[p].mp[s]=cnt;
}

void update(int no,int num) {
	while(no!=-1) {
		node[no].flag=0;
		node[no].sz+=num;
		no=node[no].par;
	}
}

 void mkdir() {//创建
 	if(node[now].mp.count(cmd.str)) {
 		cout<<"ERR"<<endl;
 		return;
	 }
	 
	 install_node(cmd.str,now);
	 update(now,1);
	 v.push_back({"MKDIR",{now,cnt}});
 	 cout<<"OK"<<endl;
 }
 
 void rm() {//删除
 	if(!node[now].mp.count(cmd.str)) {
 		cout<<"ERR"<<endl;
 		return;
	 }
	 int x=node[now].mp[cmd.str];
	 update(now,-node[x].sz);
	 node[now].mp.erase(node[x].name);
	 v.push_back({"RM",{now,x}});
	 cout<<"OK"<<endl;
 }

 
 void cd() {//切换 
 	if(cmd.str=="..") {
 		if(node[now].par==-1) {//根 
 			cout<<"ERR"<<endl;
 			return; 
		 }
		 v.push_back({"CD",{now,node[now].par}});
		 now=node[now].par;
		 cout<<"OK"<<endl;
		 return;
	} 
	 if(!node[now].mp.count(cmd.str)) {
	 	cout<<"ERR"<<endl;
	 	return;
	 }
	 int x=node[now].mp[cmd.str];
	 v.push_back({"CD",{now,x}});
	 now=x;
	 cout<<"OK"<<endl;
 }
 
 void sz() {//大小 
 	cout<<node[now].sz<<endl;
 }
 
 void ls() {//子目录列表 
 	int t=node[now].mp.size();
 	if(t==0) {
 		cout<<"EMPTY"<<endl;
 		return;
	}
	auto pos=node[now].mp.begin();
	if(t>=1&&t<=10) {
		while(pos!=node[now].mp.end()) {
			cout<<pos->first<<endl;
			pos++;
		}
		return;
	}
	if(t>10) {
		for(int i=1;i<=5;++i) {
			cout<<pos->first<<endl;
			pos++;
		}
		cout<<"..."<<endl;
		pos=node[now].mp.end();
		//反向遍历 
		for(int i=1;i<=5;++i) pos--;
		for(int i=1;i<=5;++i) {
			cout<<pos->first<<endl;
			pos++;
		}
	}
 }
 
 void undo() {
 	if(!v.size()) {
 		cout<<"ERR"<<endl;
 		return;
	}
	auto m=v[v.size()-1];
	v.pop_back();
	cout<<"OK"<<endl;
	int temp=now;
	if(m.first=="MKDIR") {
		cmd.name="RM";
		now=m.second.first;
		int x=m.second.second;
		cmd.str=node[x].name;
		update(now,-node[x].sz);
		node[now].mp.erase(node[x].name);
		now=temp;
	}else if(m.first=="RM") {
		//cmd.name="MKDIR";
		now=m.second.first;
		int x=m.second.second;
		//cmd.str=node[x].name;
		update(now,node[x].sz);
		node[now].mp[node[x].name]=x;
		now=temp;
	}
	else now=m.second.first;
 }
 
 void pushdown(int x);
 
 void pretrack(int x) {
 	node[x].pre.push_back(node[x].name);
 	if(node[x].sz==1) return;
 	if(node[x].sz<=10) {
 		for(auto i:node[x].mp) {
 			if(!node[i.second].flag) pushdown(i.second);
 			node[x].pre.insert(node[x].pre.end(),node[i.second].pre.begin(),node[i.second].pre.end());
		}
		return;	
	}
	int cnt=1;
	for(auto i:node[x].mp) {
		if(!node[i.second].flag) pushdown(i.second);
		for(auto j:node[i.second].pre) {
			node[x].pre.push_back(j);
			cnt++;
			if(cnt>=5) break;
		}
		if(cnt>=5) break;
	}
 }
 
 void bcktrack(int x) {
 	int cnt=0;
 	auto it=node[x].mp.end();it--;
 	for(;;--it) {
 		int u=it->second;
 		if(!node[u].flag) pushdown(u);
 		for(int i=node[u].bck.size()-1;i>=0;--i) {
 			node[x].bck.push_back(node[u].bck[i]);
 			cnt++;
 			if(cnt>=5) {
 				reverse(node[x].bck.begin(),node[x].bck.end());
 				break;
			}
		}
		if(cnt>=5) break;
		if(it==node[x].mp.begin()) break;
	}
 }
 
 void pushdown(int x){
 	//构造pre和bak 
 	node[x].pre.clear();
 	node[x].bck.clear();
 	pretrack(x);//正向找前几个
	if(node[x].sz>10) bcktrack(x);//反向找后几个
	else node[x].bck=node[x].pre;
	node[x].flag=1; 
 }
 
 void tree() {
 	if(!node[now].flag) pushdown(now);
 	int t=node[now].sz;
 	if(t==1) cout<<"EMPTY"<<endl;
 	else if(t>1&&t<=10) {
 		for(int i=0;i<node[now].pre.size();++i)
 			cout<<node[now].pre[i]<<endl;
	}else {
		for(int i=1;i<=5;++i)
			cout<<node[now].pre[i-1]<<endl;
		cout<<"..."<<endl;
		for(int i=5;i>=1;--i)
			cout<<node[now].bck[node[now].bck.size()-i]<<endl;
	}
 }
 
 void init() {
	cnt=0;now=0; 
	v.clear();
	node[0].init("root",-1);
}

 int main() {
 	cin>>T;
 	while(T--) {
 		cin>>Q;
 		init();
 		for(int i=1;i<=Q;++i) {
 			string s;
 			cin>>s;
 			cmd.init(s);
 			int t=cmd.type;
 			if(t==0) mkdir();
 			else if(t==1) rm();
 			else if(t==2) cd();
 			else if(t==3) sz();
 			else if(t==4) ls();
 			else if(t==5) tree();
 			else if(t==6) undo(); 
		}
	}
	return 0;
}

hint:附英文原版题面

在这里插入图片描述

B-东东学打牌

题目描述

最近,东东沉迷于打牌。所以他找到 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 行,即这次全场人的排名。

样例

Input

3
DongDong AAA109
ZJM 678910
Hrz 678910

Output

Hrz
ZJM
DongDong

解题思路

  • 设置player结构体,包含玩家姓名,手上“一手牌”的类型,该类型下的得分
  • 结构体重写<,类型优先,分数其次,名字字典序最后(注意char*要用strcmp来比较字符串)
  • 对“一手牌”的字符串用string输入,遇到2-9不做处理直接存入temp数组(存放每个玩家手中的牌,用int表示),遇到特殊的’A’,‘J’,‘Q’,‘K’直接按1,11,12,13存储,遇到’1’说明这里想表示’10’,直接存储10,同时枚举位置后移一位j++,跳过此后的’0’
  • 对每个player的temp中的牌排序,小牌在前,根据优先级(牌型值的大小)顺序判断这手牌的牌型
  • 判断牌型时的打分时关键这里我给牌型相同时的优先比较的那些牌的值更大的“权重”,比如炸弹时,炸弹的大小的权重为100,而剩下的牌权重为1;三带二时,“三个”的权重为100,剩下的两张牌权重为1(这里设置100是因为牌的值最大为13,设为10不足以满足优先级判定大小的加权需求)…最终当牌型相同时,可直接根据它的“分数”来比较两手牌的大小
  • 最终对每位player的牌排序后输出姓名即可

这里注意多组数据,且每个选手的信息输入前要初始化temp数组

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>

using namespace std;

const int maxn=100100;

struct player{
	char name[20];
	int type;
	int score;
	bool operator<(const player &p) const {
		if(type!=p.type) return type>p.type;
		else if(score!=p.score) return score>p.score;
		else if(strcmp(name,p.name)<0) return true;
		else return false;
	}
};

player rank_p[maxn];
int n,temp[10];

int main() {
	while(cin>>n){
		for(int i=1;i<=n;++i) {
			memset(temp,0,sizeof(temp));
			char s1[20];string s2;
			int cnt=0;
			scanf("%s",s1);cin>>s2; 
			strcpy(rank_p[i].name,s1);
			for(int j=0;j<s2.size();j++) {
				char c=s2[j];
				if(c=='A') temp[++cnt]=1;
				else if(c=='J') temp[++cnt]=11;
				else if(c=='Q') temp[++cnt]=12;
				else if(c=='K') temp[++cnt]=13;
				else if(c=='1') {
					temp[++cnt]=10;
					j++;
				}else temp[++cnt]=c-'0';
			}
			sort(temp+1,temp+6);
			//顺子 
			if(temp[1]==1&&temp[2]==10&&temp[3]==11&&temp[4]==12&&temp[5]==13) 
				rank_p[i].type=8,rank_p[i].score=100;
			else if((temp[1]+1==temp[2])&&(temp[2]+1==temp[3])&&(temp[3]+1==temp[4])&&
					(temp[4]+1==temp[5])) rank_p[i].type=7,rank_p[i].score=temp[5];
			//炸弹		
			else if(temp[2]==temp[5])
					rank_p[i].type=6,rank_p[i].score=temp[2]*100+temp[1];
			else if(temp[1]==temp[4])
					rank_p[i].type=6,rank_p[i].score=temp[1]*100+temp[5];
			//三带二	
			else if(temp[1]==temp[3]&&temp[4]==temp[5])
					rank_p[i].type=5,rank_p[i].score=temp[1]*100+temp[5];
			else if(temp[1]==temp[2]&&temp[3]==temp[5])
					rank_p[i].type=5,rank_p[i].score=temp[3]*100+temp[1];
			//三个		
			else if(temp[1]==temp[3])
					rank_p[i].type=4,rank_p[i].score=temp[1]*100+temp[4]+temp[5];
			else if(temp[2]==temp[4])
					rank_p[i].type=4,rank_p[i].score=temp[2]*100+temp[1]+temp[5];
			else if(temp[3]==temp[5])
					rank_p[i].type=4,rank_p[i].score=temp[3]*100+temp[1]+temp[2];
			//两对		
			else if(temp[1]==temp[2]&&temp[3]==temp[4])
					rank_p[i].type=3,rank_p[i].score=temp[3]*1e4+temp[1]*1e2+temp[5];
			else if(temp[1]==temp[2]&&temp[4]==temp[5])
					rank_p[i].type=3,rank_p[i].score=temp[4]*1e4+temp[1]*1e2+temp[3];
			else if(temp[2]==temp[3]&&temp[4]==temp[5])
					rank_p[i].type=3,rank_p[i].score=temp[4]*1e4+temp[2]*1e2+temp[1];
			//对子 
			else if(temp[1]==temp[2]) 
					rank_p[i].type=2,rank_p[i].score=temp[1]*1e2+temp[3]+temp[4]+temp[5];
			else if(temp[2]==temp[3]) 
					rank_p[i].type=2,rank_p[i].score=temp[2]*1e2+temp[1]+temp[4]+temp[5];
			else if(temp[3]==temp[4]) 
					rank_p[i].type=2,rank_p[i].score=temp[3]*1e2+temp[1]+temp[2]+temp[5];
			else if(temp[4]==temp[5]) 
					rank_p[i].type=2,rank_p[i].score=temp[4]*1e2+temp[1]+temp[2]+temp[3];
			else rank_p[i].type=1,rank_p[i].score=temp[1]+temp[2]+temp[3]+temp[4]+temp[5];
		}
		sort(rank_p+1,rank_p+n+1);
		for(int i=1;i<=n;++i) 
			cout<<rank_p[i].name<<endl;
	}
	return 0;
}

C-签到题

题目描述

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

输入格式

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

输出格式

输出 mn 和 mx

Example

Input

3
7
1
6
1

Output

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的最大值mx很容易,在输入a[i]时,找到当前椅子上人数的最大值temp,再让新来的y个人全部坐到该椅子上去,mx即=temp+y
  • 找k的最小值,首先我们要明白无论再来多少人,mn一定>=当前人数最大值temp,现在考虑新来的y个人的坐法,如果存在某种分配方法使新来的y人坐到人数!=temp的椅子(即小于最大值)上,且最终满足坐好后这些椅子上的人数不大于temp,那么k的最小值即为temp。若无法满足temp*x>=sum(已有的人数)+y,说明仍有新来的人未坐下,则设定的人数最大值的最小值小了,temp++,每轮重新执行上述判定,直到满足,此时的temp即为k的最小值mn。

代码

#include<iostream>
#include<cstdio>

using namespace std;

int x,y,a[110],temp,mn,mx,sum;

int main() {
	scanf("%d%d",&x,&y);
	for(int i=1;i<=x;++i) {
		scanf("%d",a+i);
		if(temp<a[i]) temp=a[i];
		sum+=a[i];
	}
	mx=temp+y;
	while(1) {
		if(temp*x>=(sum+y)) break;
		else temp++;
	}
	mn=temp;
	printf("%d %d\n",mn,mx);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值