week9-作业/A - 咕咕东的目录管理器/B-东东打牌/C-签到题

A - 咕咕东的目录管理器

题目:

咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序

现在咕咕东可以在命令行下执行以下表格中描述的命令:
在这里插入图片描述
在这里插入图片描述

输入:

输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 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

题目分析:

分析命令:

  1. 首先对每条指令,每个指令有指令的类型以及MKDIR,RM,CD三个指令还有参数,所以用Command结构体进行封装。

  2. 又因为目录管理器是一棵目录树的数据结构,所以需要维护该树,又因为要求字典序,所以要用map<string,目录>,它可以根据key也就是string 在内部进行排序,用类Directory表示。

  3. 具体实现:

  • MKDIR创建子目录操作:创建子目录并返回,创建失败返回空指针。
  • RM删除子目录操作:删除子目录并返回,删除失败返回空指针。
  • CD进入子目录操作:当遇到…的时候,返回其父节点即上一级目录。
  • SZ输出当前目录的大小操作:输出当前指针所在的subtreesize即可。
  • LS输出多行表示当前目录的直接目录名操作:当当前目录长度为0时,说明当前目录为空,输出EMPTY;当子目录数量小于等于10的时候,迭代器遍历全部输出;当子目录数量大于10的时候,分别用迭代器从前遍历输出前5个和用迭代器从后遍历,输出后5个。
  • TREE输出以当前目录为根的子树的前序遍历操作:这是最难的一部分,因为在后代节点数量大于10的时候,要开始前序遍历和后序遍历, 并且对复杂度有要求! TREE的命令条数最多约为1e5,此时整个树最多5000 个节点 , 20 * 1e5 * 5000,你发现可能会TLE 。所以需要用到缓存(懒更新),节点数远少于TREE 操作数,指不定还有重复询问,对于目录相同期间问过的相同问题,理应只有一次是计算过程。保存当前节点点的“十个后代”,记录当前节点的子树有无变动,无变动则“十个后代”无需更新,更新操作分为更新全桶,更新前序几个和更新后序几个。
  • UNDO撤销操作:撤销操作针对三个操作,MKDIR,RM,CD;且这三种操作必须是执行成功才能执行撤销。它们的撤销操作分别为删掉rm,添加addchild,回退。为了执行UNDO 需要保存每条指令执行的执行结果,保存到-> struct Command{…}中。

代码:

#include<iostream>
#include<stdio.h>
#include<algorithm> 
#include<map>
#include<string>
#include<vector>
using namespace std;
char tmps[20];//声明一个辅助输入的字符串数组

struct Directory
{
	string name;//当前目录的名字
	map<string, Directory*>children;
	Directory* parent;//以备CD.. 返回上级目录
	int subtreeSize;//以备sz要输出子树大小
	vector<string>* tenDescendants;//保存当前节点的十个后代
	Directory(string name, Directory* parent) 
	{
		this->name = name;
		this->parent = parent;
		this->subtreeSize = 1;
		this->tenDescendants=new vector<string>;		
	}	
	bool updated;//记录当前节点的子孙有无变动,无变动则十个后代无需更新
public:
	void maintain(int delta) //向上维护子树大小
	{
		updated = true;
		subtreeSize += delta;
		if (parent != nullptr)
		{
			parent->maintain(delta);	
		}
	}
	void tree() 
	{
		if (subtreeSize == 1) printf("EMPTY\n");
		else if (subtreeSize <= 10) 
		{			
			if (this->updated) 
			{				
				tenDescendants->clear();				
				treeAll(tenDescendants);
				this->updated = false;
			}
			for (int i = 0; i < subtreeSize; i++)
			{
				printf("%s\n", tenDescendants->at(i).c_str());
			}	
		}
		else 
		{			
			if (this->updated) 
			{
				tenDescendants->clear();
				treeFirstSome(5, tenDescendants);
				treeLastSome(5, tenDescendants);
				this->updated = false;
			}
			for (int i = 0; i < 5; i++)
			{
				printf("%s\n", tenDescendants->at(i).c_str());
			}	
			printf("...\n");
			for (int i = 9; i >= 5; i--)
			{
				printf("%s\n", tenDescendants->at(i).c_str());
			}		
		}
	}	
	Directory* getChild(string name) //取子目录并返回,不存在返回空指针
	{
		auto it = children.find(name);
		if (it == children.end()) return nullptr;
		return it->second;
	}
	Directory* mkdir(string name) //创建子目录并返回,创建失败返回空指针
	{
		if (children.find(name) != children.end()) return nullptr;		 
		Directory* ch = new Directory(name, this);
		children[name] = ch;
		maintain(+1);
		return ch;
	}	
	Directory* rm(string name) 	//删除子目录并返回,删除失败返回空指针
	{
		auto it = children.find(name);
		if (it == children.end()) return nullptr;		
		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 sz() 
	{
		printf("%d\n", this->subtreeSize);
	}
	void ls() 
	{
		int sz = children.size();		
		if (sz == 0) printf("EMPTY\n");
		else if (sz <= 10)
		{
			for (auto& entry : children)
			{
				printf("%s\n", entry.first.c_str());
			}	
		}	
		else 
		{
			auto 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());
			}
		}
	}
private:	
	void treeAll(vector<string>* bar) 	//更新全桶
	{		
		bar->push_back(name);
		for (auto &entry : children)
		{
			entry.second->treeAll(bar);
		}		
	}
	void treeFirstSome(int num, vector<string>* bar)//更新前序几个
	{
		bar->push_back(name);
		if (--num == 0) return;
		int n = children.size();		
		auto it = children.begin();
		while (n--) 
		{
			int sts = it->second->subtreeSize;
			if (sts >= num) 
			{
				it->second->treeFirstSome(num, bar);
				return;
			}
			else 
			{
				it->second->treeFirstSome(sts, bar);
				num -= sts;
			}
			it++;
		}
	}
	void treeLastSome(int num, vector<string>* bar)//更新后序几个 
	{
		int n = children.size();		
		auto 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);
	}
};

struct Command
{
	int type;//命令的类型
	string arg;//命令的参数
	const string CMDNAMES[7] = { "MKDIR","RM","CD","SZ","LS","TREE","UNDO" };
	Command(string s) 
	{//构造函数
		for(int i=0;i<7;i++)
			if (CMDNAMES[i] == s) 
			{
				type = i;
				if (i < 3) 	//MKDIR、RM、CD的参数后续读入
				{
					scanf("%s", tmps), arg = tmps;
				}
				return;
			}
	}
	Directory* tmpDir;//记录刚刚操作涉及的目录节点
};

void solve()
{
	int n;
	cin>>n;
	//scanf("%d",&n); //每组数据有m行命令
	Directory *now = new Directory("root", nullptr);
	vector<Command*>cmdList;//新增加的数组存成功执行的命令以备undo
	while (n--) 
	{
		scanf("%s", tmps);
		Command* cmd = new Command(tmps);
		switch (cmd->type)
		{
			case 0://MKDIR
				{
					cmd->tmpDir = now->mkdir(cmd->arg);
					if (cmd->tmpDir == nullptr) printf("ERR\n");
					else 
					{
						printf("OK\n");
						cmdList.push_back(cmd);
					}
					break;
				}
			case 1://RM
				{
					cmd->tmpDir = now->rm(cmd->arg);
					if (cmd->tmpDir == nullptr) printf("ERR\n");
					else 
					{
						printf("OK\n");
						cmdList.push_back(cmd);
					}
					break;
				}
			case 2://CD
			{
				Directory * ch = now->cd(cmd->arg);
				if (ch == nullptr)printf("ERR\n");
				else 
				{
					printf("OK\n");
					cmd->tmpDir = now;
					now = ch;
					cmdList.push_back(cmd);
				}
				break;
			}		
			case 3://SZ
				now->sz(); 
				break;
			case 4://LS
				now->ls(); 
				break;
			case 5://TREE
				now->tree(); 
				break;
			case 6://UNDO
			{
				bool success = false;//undo执行成功与否
				while (!success && !cmdList.empty()) 
				{
					cmd = cmdList.back(); cmdList.pop_back();
					switch (cmd->type)
					{
						case 0://UNDO MKDIR 
							success = now->rm(cmd->arg) != nullptr; 
							break;
						case 1://UNDO RM
							success = now->addChild(cmd->tmpDir); 
							break;
						case 2:	//UNDO CD
							now = cmd->tmpDir; 
							success = true; 
							break;
					}
				}
				printf(success ? "OK\n" : "ERR\n");
			}		
		}
	}
	printf("\n");
}
int main()
{
	int T;
	//scanf("%d",&T);//1、测试有T组数据
	cin>>T;
	while(T--) 
	{
		solve(); 
	}
	return 0; 
}

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 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 5 张牌的大小总和。
对子:5 张牌中有 2 张牌的值相等。如果 α 和 β 都是对子,比较这个 “对子” 的大小,如果 α 和 β 的 “对子” 大小相等,那么比较剩下 3 张牌的总和。
两对:5 张牌中有两个不同的对子。如果 α 和 β 都是两对,先比较双方较大的那个对子,如果相等,再比较双方较小的那个对子,如果还相等,只能比较 5 张牌中的最后那张牌组不成对子的牌。
三个:5 张牌中有 3 张牌的值相等。如果 α 和 β 都是 “三个”,比较这个 “三个” 的大小,如果 α 和 β 的 “三个” 大小相等,那么比较剩下 2 张牌的总和。
三带二:5 张牌中有 3 张牌的值相等,另外 2 张牌值也相等。如果 α 和 β 都是 “三带二”,先比较它们的 “三个” 的大小,如果相等,再比较 “对子” 的大小。
炸弹:5 张牌中有 4 张牌的值相等。如果 α 和 β 都是 “炸弹”,比较 “炸弹” 的大小,如果相等,比较剩下那张牌的大小。
顺子:5 张牌中形成 x, x+1, x+2, x+3, x+4。如果 α 和 β 都是 “顺子”,直接比较两个顺子的最大值。
龙顺: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

题目分析:

首先我认为该题的关键在于对不同类型的牌型的排序方式,总的来看对八种牌型的多关键字排序最多有4个关键字,所以对每个玩家构建一个结构体,变量包括该玩家手中的牌值数组card[],牌型status,玩家姓名name,以及三个排序关键字b,c,d,重构<,令其先按牌型值status升序排序,牌型值相同则依次按照b,c,d,进行降序排序,最后再按name的字典序升序排序。

然后正确的将输入的牌转换为题目要求的牌值大小,然后再判断牌型,并根据牌型更新关键字,最后排序即可。

(判断牌型的时候一定要仔细!非常仔细!!!)

代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+3;
struct player
{
	string name;//姓名
	int card[6];//牌面
	int status;//一手牌的类型 
	int b,c,d;//分别表示两张牌 三张牌 一张牌的大小 	
}poke[maxn];

bool cmp(player x,player y)
{
	if(x.status!=y.status)
	{
		return x.status>y.status;
	}
	else if(x.b!=y.b)
	{
		return x.b>y.b; 
	}
	else if(x.c!=y.c)
	{
		return x.c>y.c;
	}
	else if(x.d!=y.d)
	{
		return x.d>y.d;
	}
	else
	{
		return x.name<y.name;
	}
} 	

void trans(string s,int cnt)//牌面值的转换 
{
	memset(poke[cnt].card,0,sizeof(poke[cnt].card)); 
	int k=0;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]=='A')
		{
			poke[cnt].card[k]=1;
		}
		else if(s[i]>='2'&&s[i]<='9')
		{
			poke[cnt].card[k]=s[i]-'0';
		}
		else if(s[i]=='1')
		{
			poke[cnt].card[k]=10;
			i++;
		}
		else if(s[i]=='J')
		{
			poke[cnt].card[k]=11;
		}
		else if(s[i]=='Q')
		{
			poke[cnt].card[k]=12;
		}
		else if(s[i]=='K')
		{
			poke[cnt].card[k]=13;
		}
		k++;
	 } 	
} 

void solve(int cnt)//对每个人手中的牌进行得分的判断 
{
	sort(poke[cnt].card,poke[cnt].card+5);
	int sum=0;
	for(int i=0;i<5;i++)
	{
		sum+=poke[cnt].card[i];
	}
	bool shunzi=true;
	for(int i=0;i<4;i++)
	{
		if(poke[cnt].card[i+1]!=poke[cnt].card[i]+1)
		{
			shunzi=false;
		}
	}
	int num[5];	
	memset(num,0,sizeof(num));
	for(int i=0;i<5;i++)
	{
		for(int j=0;j<5;j++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[j])
			{
				num[i]++;
			}
		}
	}
	sort(num,num+5); 

	if(poke[cnt].card[0]==1&&poke[cnt].card[1]==10&&poke[cnt].card[2]==11&&poke[cnt].card[3]==12&&poke[cnt].card[4]==13)
	{
		poke[cnt].status=8;//8.龙顺子 
	}
	else if(num[0]==1&&num[1]==2&&num[4]==2)
	{
		poke[cnt].status=3;//3.两队 
		int *maxx=new int[5];int r=0;
		memset(maxx,0,sizeof(maxx));
		for(int i=0;i<4;i++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[i+1])
			{
				maxx[r]=poke[cnt].card[i];
				r++;
			}	
		}
		poke[cnt].b=maxx[1];
		poke[cnt].c=maxx[0];
		poke[cnt].d=sum-2*maxx[1]-2*maxx[0];
	}
	else if(num[4]==2&&num[1]==1)
	{
		poke[cnt].status=2;//2.对子 
		int maxx=0;
		for(int i=0;i<4;i++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[i+1])
			{
				maxx=poke[cnt].card[i];
			}	
		}
		poke[cnt].b=maxx;
		poke[cnt].c=sum-2*maxx;
	}	
	else if(num[4]==3&&num[0]==2)
	{
		poke[cnt].status=5;//三带二 
		int maxx=0;
		for(int i=0;i<4;i++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[i+1]&&poke[cnt].card[i+1]==poke[cnt].card[i+2])
			{
				maxx=poke[cnt].card[i];
			}	
		}
		poke[cnt].b=maxx;
		poke[cnt].c=(sum-3*maxx)/2;
	}
	else if(num[4]==3&&num[0]==1&&num[1]==1)
	{
		poke[cnt].status=4;//三个 
		int maxx=0;
		for(int i=0;i<4;i++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[i+1])
			{
				maxx=poke[cnt].card[i];
			}	
		}
		poke[cnt].b=maxx;
		poke[cnt].c=sum-3*maxx;
	}
	else if(num[4]==4&&num[0]==1)
	{
		poke[cnt].status=6;//炸弹
		int maxx=0;
		for(int i=0;i<4;i++)
		{
			if(poke[cnt].card[i]==poke[cnt].card[i+1])
			{
				maxx=poke[cnt].card[i];
			}	
		}
		poke[cnt].b=maxx;
		poke[cnt].c=sum-4*maxx;
	}
	else if(shunzi)
	{
		poke[cnt].status=7;
		poke[cnt].b=poke[cnt].card[4];
	} 
	else
	{
		poke[cnt].status=1;
		poke[cnt].b=sum;
	}	
}
int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)
		{
			string s2;//s1名字 s2手中的牌 
			cin>>poke[i].name>>s2; 
			poke[i].status=poke[i].b=poke[i].c=poke[i].d=0;
			trans(s2,i);
			solve(i);
		}
		sort(poke,poke+n,cmp);
		for(int i=0;i<n;i++)
		{
			cout<<poke[i].name<<endl;
		}
	}
	return 0;
} 
/*
5
DongDong AAA109
ZJM 678910
HRZ 78910J
TT 10JQKA
XX AAA56
*/

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。

题目分析:

  • 首先 最大值无疑就是让y个人全部坐在a[i]最大的那个长椅上,即mx=k+y。
  • 然后最小值就是要让每个长椅上的人尽可能地均匀,有两种情况:
    (1)当y不足以分配给<k的其他长椅时,那么最小值就是k;
    (2)当y可以分配给<k的其他长椅时,先分配,即让所有的长椅的人数都达到k,然后剩下的人平均分给x个长椅,最后最多的那个长椅人数即为mn。

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
	int x;
	cin>>x;//长椅数目
	int *a=new int[x];
	int y;
	cin>>y;//有 y 个人来到公园
	int mn=0,mx=0;
	int k=0;
	for(int i=0;i<x;i++)
	{
		cin>>a[i];//初始时公园长椅上坐着的人数
		if(a[i]>k)
		{
			k=a[i];
		}
	}
	int ans=0,cnt=0;
	for(int i=0;i<x;i++)
	{
		if(a[i]==k)
		{
			ans++;
		}
		else
		{
			cnt+=a[i];
		}
	}
	if((cnt+y)>(x-ans)*k)
	{
		int temp=y-((x-ans)*k-cnt);
		if(temp%x==0)
		{
			mn=temp/x+k;
		}
		else
		{
			mn=temp/x+1+k;
		}	
	}
	else
	{
		mn=k;
	}
	mx=k+y;
	cout<<mn<<" "<<mx<<" "<<endl;
	return 0;	
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值