程序设计——第九周(复杂模拟题的普适性方法:目录管理、一手牌、选长椅))

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

思路分析

这道题是跟着助教的讲解走的,后期写代码也是参照PPT写出来的,这类型题目很有难度,但是写下来发现只要有清晰的思路,有着清晰的设计安排,就会容易得多。
正如助教的讲解所说,不知道如何入手,就先拟定框架,再一步一步完善。题目中主要涉及两个对象:指令和文件。对于指令可以定义int型成员type表示所执行的指令是哪一种,方便主函数中对操作进行switch case,定义string类型arg表示指令的具体操作,定义文件指针类型tmpdir记录当前对文件进行操作的节点,根据其值是否为空判断指令是否成功执行。在dira结构体中,定义数据成员有string类型的name表示文件名,定义map类型的children表示文件的孩子。定义指针par表示父亲方便执行CD定义dirasize表示当前目录的大小方便执行SZ,定义vector类型的tendescendants保存当前的10个后代,方便执行LS。定义updated表示是否之前已经有遍历操作,如果执行LS操作时,updated为真就要先把tendescendants清空,再来重新存储新的一轮遍历。定义公有函数getchild取出子目录并且返回这个节点。定义mkdir函数创建子目录即在children数组末尾加上新节点即可并且返回这个创建的节点。定义rm函数找到要删除的节点将其删除并且返回这个被删除的节点。另外还定义cd、addchild、maintain、sz、ls、tree等函数。这里只是想要记录一下做这道题的那种思维方向,重点并不是函数细节。函数细节在代码中已经很详细了。

代码实现

#include<bits/stdc++.h>
using namespace std;
char tmps[20];//记录当前操作 


struct dira{
	string name;//当前的目录名
	map<string,dira*> children;//子目录
	dira *par;//上一级目录,便于执行CD
	int dirasize;//当前目录的大小即1+孩子个数 
	bool updated;
	vector<string> tendescendants;//保存当前的10个后代(或者前5个和后5个) 
	dira(string thename,dira* thepar){
		name=thename;
		par=thepar;
		dirasize=1;//目前只有一个父节点 
	}
	public: 
	    dira* getchild(string s);
	    dira* mkdir(string s);
	    dira* rm(string s);
	    dira* cd(string s);
	    bool addchild(dira* ch);
	    void maintain(int delta);//向上维护子树大小 
	    void sz();
	    void ls();
	    void tree();
	    void clear();
	    
	    
	private:
		void treeall(vector<string>& bar);//全部后代插入桶
		void treefirstsome(int num,vector<string> &bar);//前序遍历并加入首要的num个 后代
		void treelastsome(int num,vector<string> &bar);//后序遍历并且加入首要的num个后代 
		//既然tendscendants是vector数组类型,那就不要忘记这些函数中的参数要加引符号
};

struct command{
	const string cmdnames[7]={"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
	int type;//命令类型
	string arg;// 命令的参数 
	dira* tmpdir;//记录上一个操作的目的节点 
	command(string s){
		for(int i=0;i<7;i++) 
		    if(cmdnames[i]==s){
			type=i;
			if(i<3) {scanf("%s",tmps);arg=tmps;}//MKDIR,RM,CD操作之后有这些参数 
			return;
		}
    } 
};

list<command*> cmdlist;

dira* dira::getchild(string s){
	//取子目录并返回,不存在返回空指针 
	map<string,dira*>::iterator it=children.find(s);
	if(it==children.end())  return NULL;
	return it->second;
}

dira* dira::mkdir(string s){
	//创建子目录并返回,创建失败则返回空指针
	if(children.find(s)!=children.end()) return NULL;
	dira* ch=new dira(s,this);
	children[s]=ch;
	//tmpdir=ch;
	maintain(+1);
	return ch; 
}

dira* dira::rm(string s){
	auto it=children.find(s);
	if(it==children.end()) return NULL;
	maintain(-1*it->second->dirasize);
//	tmpdir=it.second;
	children.erase(it);
	return it->second;
}

dira* dira::cd(string s){
	if(".."==s) return this->par;
	return getchild(s);
}

bool dira::addchild(dira*ch){
	//加入子目录并返回成功与否
	if(children.find(ch->name)!=children.end()) return false;
	children[ch->name]=ch;

	maintain(+ch->dirasize);
	return true; 
}

void dira::maintain(int delta){
	//向上维护子树大小
	updated=true;//已经做了修改 
	dirasize+=delta;
	if(par!=NULL)
	    par->maintain(delta); 
}

void dira::sz(){
	printf("%d\n",this->dirasize);
}

void dira::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{
	    map<string,dira*>::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 dira::tree(){
	if(dirasize==1) printf("EMPTY\n");
	else if(dirasize<=10){
		if(this->updated){
			tendescendants.clear();
			treeall(tendescendants);
			this->updated=false;
		}
		for(int i=0;i<dirasize;i++){
			printf("%s\n",tendescendants.at(i).c_str());
		}
	}
	else{
		if(this->updated){
			tendescendants.clear();
		    //tendescendants=NULL;
			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());
	}
} 


void dira::treeall(vector<string>& bar){
	//更新全桶
	bar.push_back(name);//???????
	for(auto& entry:children) entry.second->treeall(bar); 
}

void dira::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->dirasize;
		if(sts>=num){
			it->second->treefirstsome(num,bar);
			return;
		}
		else{
			it->second->treefirstsome(sts,bar);
			num-=sts;
		}
		it++;
	} 
}

void dira::treelastsome(int num,vector<string>& bar){
	//更新后序几个
	int n=children.size();
	auto it=children.end();
	while(n--){
		it--;
		int sts=it->second->dirasize;
		if(sts>=num){
			it->second->treelastsome(num,bar);
			return;
		}
		else{
			it->second->treelastsome(sts,bar);
			num-=sts;
		}
	}
	bar.push_back(name);
}

void dira::clear(){
	tendescendants.clear();
	children.clear();
}
	    

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		cmdlist.clear();
		dira* now=new dira("root",nullptr);//定义当前目录 
		int n;
		scanf("%d",&n);
		while(n--){
		    scanf("%s",tmps);
		    dira* ch=NULL;
		    command* cmd=new command(tmps);//当前的操作 
		    switch(cmd->type){
			    case 0:cmd->tmpdir=now->mkdir(cmd->arg);
			           if(cmd->tmpdir==nullptr) printf("ERR\n");
			            else{
				            printf("OK\n");
				            cmdlist.push_back(cmd);
		           	    } 
			            break;
			    case 1:cmd->tmpdir=now->rm(cmd->arg);
			           if(cmd->tmpdir==nullptr) printf("ERR\n");
			           else{
				            printf("OK\n");
				            cmdlist.push_back(cmd);//存储 
			            }  
			            break;
			    case 2: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: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)!=nullptr;break;
						       case 1:success=now->addchild(cmd->tmpdir);break;//把删除的节点加回去
					 	       case 2:now=cmd->tmpdir;success=true;break;//返回原来所处的文件 
					       } 
				        }
				        printf(success? "OK\n":"ERR\n"); 
			    } 
		    }
		    //	= ->clear();  
	    } 
    }
	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

思路分析

这道题和上次的限时练习的打牌那道题很相似,思路很简单,就是有一点可以注意:在这里可以观察题目要求对一些变量以结构体的形式进行封装,比如这里涉及到要比较是三带二的大小的时候,要先比三张相同的牌,再比两张相同的牌,所以可以定义结构体成员thr和tw分别进行表示,这样在进行判断牌型的时候,就可以将牌的信息记录到结构体中,进行排序的时候就会简单得多。

代码分析

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
#define maxn 100005
using namespace std;

map<char,int> mp;
void initial(){
	char s[]={'A','2','3','4','5','6','7','8','9','1','J','Q','K'};
	for(int i=0;i<13;i++){
		mp.insert(make_pair(s[i],i+1));///构造数对 	
	}
}

int thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;
int get_pri(int num[]){
	int cnt=0;
	thethr=0,thetw1=0,thetw2=0,thefr=0;
	for(int i=1,j=10;i<5;i++,j++){
		if(num[i]==j) cnt++;
		else break;
	}
	if(cnt==4&&num[0]==1) {
		thermn=48;
		//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
	    return 8;//龙顺 
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;
	cnt=0;
	for(int i=0;i<4;i++){
		if(num[i]+1==num[i+1]) cnt++;
		else break;
	} 
	if(cnt==4){
		thermn=num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
	    return 7;//顺子
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//炸弹 
	if(num[3]==num[0]){
		thefr=num[3];
		thermn=num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 6;
	}
	if(num[4]==num[1]){
		thefr=num[1];
		thermn=num[0];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 6;
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//三代二
	if(num[0]==num[1]&&num[2]==num[4]){
		thethr=num[2];
		thetw1=num[0];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 5;
	} 
	if(num[0]==num[2]&&num[3]==num[4]){
		thethr=num[0];
		thetw1=num[3];
		return 5;
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//3个
	if(num[0]==num[2]){
		thethr=num[0];
		thermn=num[3]+num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 4;
	} 
	if(num[1]==num[3]){
		thethr=num[1];
		thermn=num[0]+num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 4;
	}
	if(num[2]==num[4]){
		thethr=num[2];
		thermn=num[0]+num[1];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 4;
	}

	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//两对 
	if(num[0]==num[1]&&num[2]==num[3]){
		thetw1=num[2];//存比较大的对子 
		thetw2=num[0];
		thermn=num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 3;
	}	
	if(num[0]==num[1]&&num[3]==num[4]){
		thetw1=num[3];//存比较大的对子 
		thetw2=num[0];
		thermn=num[2];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 3;
	}
	if(num[1]==num[2]&&num[3]==num[4]){
		thetw1=num[3];//存比较大的对子 
		thetw2=num[1];
		thermn=num[0];
		//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 3;
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//对子
	if(num[0]==num[1]){
		thetw1=num[0];
		thermn=num[2]+num[3]+num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 2;
	} 
	if(num[1]==num[2]){
		thetw1=num[1];
		thermn=num[0]+num[3]+num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 2;
	}
	if(num[2]==num[3]){
		thetw1=num[2];
		thermn=num[0]+num[1]+num[4];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 2;
		
	}
	if(num[3]==num[4]){
		thetw1=num[3];
		thermn=num[0]+num[1]+num[2];
	//	printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
		return 2;
	}
	
	thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//大牌 
	thermn=num[0]+num[1]+num[2]+num[3]+num[4];
	//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn); 
	return 1;
}

struct card{
	int val[5];
	int pri;//牌的类型 
	int thr;//三个相等的值 
	int tw1;//对子 三代二的二 两对 
	int tw2;//
	int fr;//炸弹 
	int rmn;
	string name;
    card(){}
	card(string nm,string cd){
		name=nm;
		int cnt=0;
		for(int i=0;cnt<5;i++) {
		    val[cnt++]=mp[cd[i]];
		    if(cd[i]=='1') i++;
			
		}
		sort(val,val+5);
		pri=get_pri(val);
		thr=thethr,tw1=thetw1,tw2=thetw2,fr=thefr,rmn=thermn;
	} 
	bool operator<(card c){
		if(pri!=c.pri) return pri>c.pri;
		else if(pri==8) return name<c.name;
		else if(pri==7){
			if(rmn!=c.rmn) return rmn>c.rmn;
			else return name<c.name;
		}
		else if(pri==6){
			if(fr!=c.fr) return fr>c.fr;
			else if(rmn!=c.rmn) return rmn>c.rmn;
			return name<c.name;
		}
		else if(pri==5){
			if(thr!=c.thr) return thr>c.thr;
			else if(tw1!=c.tw1) return tw1>c.tw1;
			return name<c.name;
		}
		else if(pri==4){
			if(thr!=c.thr) return thr>c.thr;
			else if(rmn!=c.rmn)  return rmn>c.rmn;
			return name<c.name;
		}
		else if(pri==3){
			if(tw1!=c.tw1) return tw1>c.tw1;
			else if(tw2!=c.tw2) return tw2>c.tw2;
			else if(rmn!=c.rmn) return rmn>c.rmn;
			return name<c.name;
		}
		else if(pri==2){
			if(tw1!=c.tw1) return tw1>c.tw1;
			else if(rmn!=c.rmn) return rmn>c.rmn;
			return name<c.name;
		}
		else{
			if(rmn!=c.rmn) return rmn>c.rmn;
			else return name<c.name;
		}
	}
}c[maxn];

int main(){
	initial();
	int n;
	while(~scanf("%d",&n)){
	    for(int i=0;i<n;i++){
		    string nm,cd;
		    cin>>nm>>cd;
		    c[i]=card(nm,cd);
		    
	    }
	    sort(c,c+n);
	    for(int i=0;i<n;i++) 
	       cout<<c[i].name<<"\n";
    }
	return 0;	
}

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

思路分析

这个题目一定要好好读,“k = 所有椅子上的人数的最大值,那么k可能的最大值mx和最小值mn分别是多少。”这句话一定要好好咀嚼,我因为题意没理解清除WA了好几次。找到一种坐法使得坐的人数最多的那个椅字人数达到最大,这个很简单,肯定是max(a[0:x-1])+y。现在要求最小情况mn,那么max(a[0:x-1])一定成立的,所以不妨这样思考,将y人坐之前的椅字按照人数升序排序,然后让y人将每个不足max(a[0:x-1])人的椅子坐满到max(a[0:x-1])人。那么此时,每个椅子上的人都是max(a[0:x-1])个,之后只需将y人中还剩下rmn的人平均分配到椅子中,即mn=max(a[0:x-1])+mn/x+(mn%x? 1:0)。

代码实现

#include<bits/stdc++.h>
using namespace std;

int a[110];
int main(){
	int x,y,sum=0;
	scanf("%d%d",&x,&y);
	for(int i=0;i<x;i++){
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	sort(a,a+x);
	int tp=a[x-1];//已有的最大人数 
	int mx=tp+y;
	int mn=tp;
	int lck=x*tp-sum;//先将每个椅子的人数填补到tp个 ,如果y不够填补,那么就直接输出mn,mx 
	if(y>lck) mn=tp+(y-lck)/x+((y-lck)%x?1:0);//说明能够填补满之后将剩余的数字平均填补到长椅中 
	printf("%d %d",mn,mx);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值