CSP 201903-4 (消息传递接口)两种满分思路


还有13天就考试了,害怕,吃手手。
做了7套题了,第一次练习的时候能在后面3题中拿满分,哭了。最近的收获主要有两点,一是csp的题很考验细节,时间很有限,一定要想清楚了思路和可行性再做;二是要多动手打代码排坑,有时候可能差一点点就能高分甚至满分,但没考虑道一些细节就只能得点可怜分甚至没有分了,这道题就是一个很好的例子。由于我也经常看别人代码,但能力有限,没有详尽注释很少能看懂,所以我的注释都很详细。但毕竟还是自己打才有印象。别人的代码即使能看懂也意义不大。

题目描述

传送门

试题链接

题目描述

在这里插入图片描述

耗时

在这里插入图片描述
要求1s内,两种方法都比较快

注意事项、解题思路

读题的时候就感觉用队列来做最方便(进程先进先出),这样一来就主要考察stl容器操作和对输入的处理了。抛开一切不说,这道题要求能检测换行,再对每一行输入进行处理,这里就有不少坑。由于之前用geiline被卡过时间(2020年6月第3题),我一开始不想用getline,但出现了很多错误,最后还是用getline来读取每一行。这里要注意两点:

1.慎用cin,cout,ios::sync_with_stdio(false);

一般习惯用cin、cout的同学都会加一句ios::sync_with_stdio(false);来打消iostream的输入输出缓存,可以节省许多时间,避免因为输入输出卡时间TLE,这对于有大量输入输出的题来说是很关键的。但这道题我这样做一开始只有20分,看了很多遍,代码逻辑没问题。后来偶然发现把“ios::sync_with_stdio(false);”注释掉就满分了,无语,我也不知道这是为什么。可能是和getline有什么冲突?我理论学的不扎实,希望有经验的dalao能够告知原因。喜欢用cin,cout的我已经在上面栽了太多坑了,保险起见以后还是用C来写输入输出。

2.不要忘记getchar()消除换行

无论是用scanf还是cin,这道题在输入题目要求的T,n后一定要记得getchar()消除换行,否则会影响getline的读取输入,只有40分(怎么还能有40分?我也不清楚,只能说csp的测试用例很奇妙)

解题思路

进程用结构体来表示,用队列数组存储。结构体中type(0或1)表示其是发送方还是接收方,id表示他的目标进程号。用getline读取每一行输入,处理输入得到进程信息,存入队列数组中,我们利用数组下标就是自己的进程号,结构体中就能少一个属性。
位于队首的进程是亟待满足的,且满足后就没有利用价值了可以pop(),于是有两种思路:
1.广度优先思路,每次遍历一遍队列数组,如果非空就取队首元素,通过下标直接寻找有没有可以满足其需求的进程,可以满足就pop两个进程,如果不能满足要进行讨论。如果目标进程是空进程,直接死锁,退出,如果不是,记录不可满足数量加一(其实如果目标进程是彼此但操作类型一致,也能直接判定为死锁,这是思路二中最简单的“环”,但这里不用判断)。每次遍历记录空队列和不可满足队列的数量,全空时说明不会死锁,除了空队剩下的都为不可满足时说明死锁。
2.深度优先思路,每次从队列数组中第一个不为空的队列开始判断,如果能找到可以满足其需求的进程,pop后继续判断,直到为空或不可满足,为空就退出循环,不可满足就转移到目标进程队列,如果为空直接死锁退出,否则将其作为调查对象,找其目标进程看是否能满足,并标记上一个对象为“已访问但无法解决”状态,如果这样进行下去访问到已访问的队列,说明出现了环,如果这一次访问仍然无法满足它,即出现死锁,退出。只要没出现环,最后肯定有调查的队列会变空。当全为空时说明不会死锁。

思路一代码

#include<bits/stdc++.h>
using namespace std;
struct pro{
	int type,id; //操作类型及目标进程号 
};

int main(){
	int T,n,next,countfail,countempty; //countfail:当前无法满足的进程数量 countempty:当前空进程数量 
	scanf("%d%d",&T,&n);
	getchar();//消除回车 
	string str;//每一行的输入 
	bool jud2 = true; 
	while(T--){
		queue<pro> mypro[n];//队列没有clear操作,所以每次重新声明一个,双向队列dequeue可以clear 
		jud2 = true;
		for(int i=0; i<n; i++){
			getline(cin,str);//读取行输入 
			for(int j=0; j<str.length(); j++){
				pro tool;//声明一个进程 
				tool.type = str[j]=='S'?1:0;//判断进程类型,send为1,receive为0 
				int t = 0;//目标进程号 
				j++;
				while(str[j]!=' '&&j<str.length()){//计算目标进程号 
					t = t*10+(str[j++]-'0'); 
				} 
				tool.id = t;
				mypro[i].push(tool);//进程入队 
			}
		}
		while(jud2){
			countfail = 0; //countfail:当前无法满足的进程数量 countempty:当前空进程数量 
			countempty = 0; //每次循环重置 
			for(int i=0; i<n; i++){
				if(!mypro[i].empty()){
					next = mypro[i].front().id;//目标进程号 
					if(mypro[next].empty()){//目标进程为空,死锁退出 
						printf("1\n");
						jud2 = false;
						break;
					}
					//目标进程可以满足,两进程都出队 
					else if(mypro[next].front().id==i&&mypro[next].front().type!=mypro[i].front().type){
						mypro[i].pop();
						mypro[next].pop();
					}
					else{
						countfail++;//无法满足数量加一 
					}
				}
				else countempty++;//空队数量加一 
			}
			if(countempty==n){ //全空,不死锁,退出 
				printf("0\n");
				break;
			}
			else if(countfail+countempty==n){ //除了空全无法满足,死锁,退出 
				printf("1\n");
				break;
			}
		}
	}
	return 0;
} 

思路二代码

#include<bits/stdc++.h>
using namespace std;
struct pro{
	int type,id;//操作类型及目标进程号 
};

int main(){
	int T,n,next,pre;//pre:当前研究队列号 next:目标队列号 
	scanf("%d%d",&T,&n);
	getchar();//消除回车 
	string str;//每一行的输入 
	int jud[n];//判断队列是否已经被访问 
	bool jud2 = true;
	while(T--){
		queue<pro> mypro[n];//队列没有clear操作,所以每次重新声明一个,双向队列dequeue可以clear 
		jud2 = true;
		for(int i=0; i<n; i++){
			getline(cin,str);//读取行输入 
			for(int j=0; j<str.length(); j++){
				pro tool;//声明一个进程 
				tool.type = str[j]=='S'?1:0;//判断进程类型,send为1,receive为0 
				int t = 0;//目标进程号 
				j++;
				while(str[j]!=' '&&j<str.length()){//计算目标进程号 
					t = t*10+(str[j++]-'0'); 
				} 
				tool.id = t;
				mypro[i].push(tool);//进程入队 
			}
		}
		while(jud2){
			memset(jud,0,sizeof(jud));//每次循环,判断是否已访问的数组重新赋值为未访问
			int i = 0;
			while(mypro[i].empty()&&i<n)i++;//找第一个不为空的队列,别忘了i<n,否则运行错误 
			if(i==n){	//如果全空,不会死锁,退出 
				printf("0\n");
				break;
			}
			pre = i; //当前研究的队列 
			while(!mypro[pre].empty()){ // 当前研究的队列非空时 
				next = mypro[pre].front().id;//目标进程号 
				if(mypro[next].empty()){//目标进程为空,死锁退出 
					printf("1\n");
					jud2 = false;
					break;
				}
				if(jud[next]){//目标进程之前已被访问且这一次仍然无法满足它,死锁退出,否则取消所有已访问记录 
					if(mypro[next].front().id==pre&&mypro[next].front().type!=mypro[pre].front().type){
						mypro[pre].pop();
						mypro[next].pop();
						memset(jud,0,sizeof(jud));// 取消已访问记录 
					}
					else{
						printf("1\n"); //成环无法解决,死锁 
						jud2 = false;
						break;
					}
				}
				else{//目标进程之前未被访问,若能满足就继续判断,若不能满足则标记已访问并转到目标进程 
					if(mypro[next].front().id==pre&&mypro[next].front().type!=mypro[pre].front().type){
						mypro[pre].pop();
						mypro[next].pop();
					}
					else{
						jud[pre] = 1;
						pre = next;
					}
				}
			} 
		}
	}
	return 0;
} 
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值