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;
}