问题:
https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=10&page=show_problem&problem=745
前言:
原文题面比较坑?或者说有点看不懂。所以接下来我的解释主要是我自己经过理解之后的一个意译。《算法竞赛入门经典》这本书的177页有比较贴近原文的翻译,有兴趣可以看看。
题面:
任务是模拟Petri网的运转。在这个网中会有N个P(Place),即“仓库”。还会有N个T(Transitions),即“中转站”。仓库中会有一些token(图中的小黑球),他们需要通过管道(例图中的箭头)在Petri网中移动。
运转规则是:
对于一个中转站,它的运转要求他的输入端(指向他的箭头)至少满足每个输入端有一个token。例如例图中的T2需要P2中至少有两个token才能发生运转。运转发生后,中转站的每个输入端减少一个token,它的每个输出端增加一个token。例如例图中,当P2有两个token,T2发生运转,则P2清空,P3增加一个token。注意!当有同时多个中转站满足运转要求,则可以任选一个运转,输入会保证结果唯一
请问输入的Petri网是否能运转到指定次数?
输入:
第一行:输入一个整数,代表有n个仓库
第二行:输入一个整数序列,代表n个仓库中各自存在的token
第三行:输入一个整数,代表有n个中转站
第四行:接下来有n行整数序列,其中的负数代表中转站的输入仓库序号,正数代表中转站的输出仓库序号,0代表该序列输入结束
……
最后一行:一个整数,代表需要运转的次数
共有x组这样的数据,以一行单独的0为输入结束标志
输出:
到达指定的运转次数
Case 样例序号: still live after 目标次数 transitions
Places with tokens: 仓库编号(剩余token)…… // 注意,如果一个仓库里面没有token,则不需要输出这个仓库
无法运转到指定次数
Case 样例序号: dead after 最大次数 transitions
Places with tokens: 仓库编号(剩余token)……
解题思路:
理解题面后,解题十分容易,直接按照题意处理即可。循环遍历每个中转站,如果可以运转,则运转,并将运转次数++。如果所有中转站都无法运转,则运转结束。
需要注意一下,每个Transition的输入当中可能有多个不一样序号的Place,同一个Place也可能会有多次输入同一个Transition中。输出也是类似的。所以我的解题代码当中,一个Trans的结构用map存放了序号对应的个数,并用vector记录了有哪些Place。
其次注意一下,每组数据的输出需要和下组输出之间多空一行,所以我的代码当中输出有两个换行。
以下是我的AC代码,由于本人喜欢把一些较长的函数拆分成多个,所以代码行数比较多。注释比较详细,加之本题比较简单,应该很容易看明白。
/*
问题:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=10&page=show_problem&problem=745
类型:简单模拟
时间:2020年3月5日
*/
#include <iostream>
#include <map>
#include <vector>
#include <cstring>
using namespace std;
// 一些变量或结构体定义
struct Trans{
map<int, int> inKey; // 存放输入中每个Place序号与之相应的token需求个数
vector<int> inList; // 存放输入中每个Place序号
map<int, int> outKey; // 存放输出中每个Place序号与之相应的token需求个数
vector<int> outList; // 存放输出中每个Place序号
};
int NP, NT, NF;
int npList[110];
Trans ntList[110];
// 一些函数声明
void fun(int id); // 主要函数
void initTrans(Trans &T); // 初始化一个Trans
void outToken(); // 输出所有Place中的token状态
bool run(); // 尝试运转一次
// Main
// #define debug
int main(){
#ifdef debug
freopen("input.txt", "r",stdin);
#endif
int id = 0;
cin >> NP;
while(NP){
id++;
// 处理输入
for(int i = 1; i <= NP; i++)
cin >> npList[i];
cin >> NT;
for(int i = 1; i <= NT; i++){
initTrans(ntList[i]);
int tmp;
cin >> tmp;
while(tmp){
if(tmp>0){ // 输出
if(ntList[i].outKey.count(tmp) != 1)
ntList[i].outList.push_back(tmp); // 如果之前没有过这个输出,则在序列里面添加一下这个Place的序号
ntList[i].outKey[tmp]++;
}
else{ // 输入
if(ntList[i].inKey.count(-tmp) != 1)
ntList[i].inList.push_back(-tmp); // 如果之前没有过这个输入,则在序列里面添加一下这个Place的序号
ntList[i].inKey[-tmp]++;
}
cin >> tmp;
}
}
cin >> NF;
// 测试Petri网
fun(id);
// 新的Petri网开始输入
cin >> NP;
}
return 0;
}
// 一些函数定义
void initTrans(Trans &T){
T.outKey.clear();
T.inKey.clear();
vector<int>().swap(T.inList);
vector<int>().swap(T.outList);
}
void fun(int id){
int count = 0;
while(count!=NF && run()){
count++;
}
if(count==NF)
cout << "Case "<<id<<": still live after "<<count<<" transitions"<<endl;
else
cout << "Case "<<id<<": dead after "<<count<<" transitions"<<endl;
outToken();
}
bool run(){
for(int i = 1; i <= NT; i++){ // 遍历所有的Trans,查看是否有可以运转的Trans
int tmp[110]; // 开一个临时的数组,用于暂存npList(各个Place的数据)
memcpy(tmp, npList, sizeof(int)*110);
bool success = true;
for(int x = 0; x < ntList[i].inList.size(); x++){
int tag = ntList[i].inList[x];
int num = ntList[i].inKey[tag];
if(num > tmp[tag]){ // 如果有一个Place中的token低于这个Trans的输入要求,则无法运转
success = false;
break;
}
tmp[tag]-=num;
}
if(success){
for(int x = 0; x < ntList[i].outList.size(); x++){
int tag = ntList[i].outList[x]; // 为Trans所输出的Place增加相应的token
int num = ntList[i].outKey[tag];
tmp[tag]+=num;
}
memcpy(npList, tmp, sizeof(int)*110);; // 更新状态
/*for(int x = 1; x <= NP; x++){
cout << npList[x] << " ";
}
cout << endl;*/
return true; // 这个Trans运转成功,返回真
}
}
return false; // 所有的Trans都无法正常运转,返回假
}
void outToken(){
cout << "Places with tokens:";
for(int i = 1; i <= NP; i++){
if(npList[i]>0){
cout <<" "<<i<<" ("<<npList[i]<<")";
}
}
cout << endl << endl; // 注意是两个换行!
}