题目链接:http://118.190.20.162/view.page?gpid=T86
题目思路
第一次看这道题的时候,感觉像是仅可以可在左边这个面操作的消消乐。然后每一行可以用一个队列来存储,当该行第一个元素被消掉后,只需要把它push出来就可以了。
由于思路存在以下问题,最后还是参考了别人的博客。
- 如何动态创建那么多队列(当时还没写过队列数组) ?
- 如何去模拟这个消除的过程,有什么通用的方法寻找可以消掉的两个元素?
- 如何判断当前状态能否继续消下去?
看了dalao的博客后,有了以下思路:
- 通过队列数组来存储各个进程,每次挑选一个指令不为空的进程( ),通过读取进程队列的第一个指令等待的进程编号决定下一个要访问进程( )。
- 当 要访问的进程恰好是 ,且他们分别为R和S,则把它们消掉。否则,把当前访问的进程标志为 ,然后 把 置为 的第一个指令等待的进程编号(类似于链表访问下一个元素的操作)。
- 当所有的进程队列都为空时结束模拟。
- 至于死锁判断,当等待的进程已经访问过且同为R或S(说明出现了环),或者等待的进程其指令已经为空(等待不会出现的东西),说明出现了死锁。
代码如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 10;
struct Node {
// id表示要访问进程的id
int id, sr;
};
int T, n;
string str;
queue<Node> q[maxn]; // q[i]表示第i号进程
int vis[maxn]; //用于检测有没有环(死锁)
void init() {
for (int j = 0; j < n; j++) {
getline(cin, str);
for (int i = 0; i < str.length(); i++) {
Node node;
node.sr = (str[i++] == 'R' ? 1 : 0);
int t = 0;
while (i < str.length() && str[i] != ' ')
t = t * 10 + str[i++] - '0';
node.id = t;
q[j].push(node);
}
}
}
void solve() {
int flag = 1;
while (flag) {
flag = 0;
memset(vis, 0, sizeof(vis));
int pre = 0;
while (pre < n && q[pre].empty()) pre++;
if (pre == n) break;
int next = q[pre].front().id;
vis[pre] = 1;
// 当访问已经访问过或者已经没有指令的进程,说明出现死锁。
while (!vis[next] && !q[next].empty()) {
vis[next] = 1;
// 满足消除条件
if (q[next].front().id == pre && q[next].front().sr != q[pre].front().sr) {
flag = 1;
q[next].pop();
q[pre].pop();
break;
}
// 类似于链表访问一下元素的操作
pre = next;
next = q[pre].front().id;
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
//如果进程指令不为空,说明出现死锁
if (!q[i].empty()) {
ans = 1;
// 进程指令的清空操作
while (!q[i].empty()) q[i].pop();
}
}
printf("%d\n", ans);
}
int main() {
//freopen("1.in", "r", stdin);
scanf("%d %d", &T, &n);
getchar();
while (T--) {
init();
solve();
}
return 0;
}
最后,还是谈谈踩到的坑:
- 需要把首行输入T和n的值后面的’\n’用getchar()清掉,不然会影响后面getline()的读入。
- 把进程中的指令分离时,要注意检查语句逻辑的正确性。
- 如果用set或map记录进程有没有被访问过就会超时,改成普通的数组不会超时(耗时:906ms,感觉有点勉强)。
- 差点忽略了“等待的进程其指令已经为空”这种情况。