【ccf】202009-3 点亮数字人生【拓扑排序、大模拟】

原题链接

❄️ | 题目描述

模拟一个电路的实际运行情况。

一个电路中有 m 个输入以及 n 个功能器件。在这些器件之间有线路相连,一个功能器件可能有多个输入。

这构成了一个有向图。

如果图中有环,则输出 “LOOP” 并结束

否则则模拟电路运行 S 次。每次给出 m 个输入以及 cnt 个需要输出器件值的编号(无序),需要按照编号顺序输出电路运行后这些器件的输出值。

每个检测点有多组数据,t 组。

✨ | 实现思路

拓扑排序 + 大模拟

首先根据题目的输入建图。将输入以及功能器件都看成节点,连接的电路看成有向边。

根据拓扑排序判断图是否有环。

没环的话,根据拓扑排序的顺序遍历每一个节点,模拟电路运行情况。

这里要提前保存下来输入以及要输出的器件编号。在每次运行后根据编号输出。

💓 | 具体代码

```cpp
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 3010;     // 输入 + 器件数量最多N + kN = 3000个
const int M = N * 5;   // 每个点最多 k = 5 个输入
int m, n;   // 输入数量、器件数量

int h[N], e[M], ne[M], idx;
int w[N];   // 第 i 个器件的值  [1 - m的输入器件  m+1 - n的功能器件]
int f[N];   // 第 i 个器件的功能

vector<int> in[M], out[M];  // 第 i 次运行的所有输入、所有要输出的器件编号

int q[N], d[N];     // 拓扑排序队列、入度

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
    d[b] ++;    // b的入度 ++
}

int get(char *str)
{
    string name[] = {"AND", "OR", "NOT", "XOR", "NAND", "NOR"}; // 1, 0, 0, 0, 1, 0
    for (int i = 0; i < 6; i ++)
        if (name[i] == str) return i;
    return -1;
}

bool topsort()
{
    int hh = 0, rr = -1;
    for (int i = 1; i <= m + n; i ++)
        if (d[i] == 0) q[++ rr] = i;
    
    while (hh <= rr)
    {
        int u = q[hh ++];
        for (int i = h[u]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (-- d[j] == 0) q[++ rr] = j;
        }
    }
    
    return rr == m + n - 1;
}

int main()
{
    int T;
    cin >> T;
    while (T --)
    {
        memset(h, -1, sizeof h);
        idx = 0;
        memset(d, 0, sizeof d);
        memset(q, 0, sizeof q);
        
        cin >> m >> n;
        
        char str[100];
        int cnt;
        
        for (int i = 1; i <= n; i ++)
        {
            cin >> str >> cnt;
            f[m + i] = get(str);
            while (cnt --)
            {
                cin >> str;
                int t = atoi(str + 1);
                if (str[0] == 'I') add(t, m + i);
                else add(m + t, m + i);
                
                /*
                    图中共 m + n 个节点,前m个是输入
                    Im:表示第 m 个输入信号连接到此输入端,保证 1≤m≤M;
                    On:表示第 n 个器件的输出连接到此输入端,保证 1≤n≤N。       
                */
            }
        }
        
        int S;
        cin >> S;
        for (int i = 1; i <= S; i ++)
        {
            in[i].clear();
            for (int j = 1; j <= m; j ++)
            {
                int x;
                cin >> x;
                in[i].push_back(x);
            }
        }
        
        for (int i = 1; i <= S; i ++)
        {
            out[i].clear();
            int cnt; cin >> cnt;
            while (cnt --)
            {
                int x; cin >> x;
                out[i].push_back(x);
            }
        }
        
        
        if (!topsort()) 
        {
            puts("LOOP");
            continue;
        }
        
        
        for (int k = 1; k <= S; k ++)   // 运行S次
        {
            // 初始化输入
            for (int i = 1; i <= m; i ++)
                w[i] = in[k][i - 1];
            
            // 每次运行前初始化器件值,保证不影响计算
            for (int i = m + 1; i <= m + n; i ++)
            {
                if (f[i] == 0 || f[i] == 5) w[i] = 1;
                else w[i] = 0;
            }
            
            for (int i = 0; i < m + n; i ++)
            {
                int u = q[i], t = w[u];
                // 枚举通向的下一个器件
                for (int j = h[u]; j != -1; j = ne[j])
                {
                    // "AND", "OR", "NOT", "XOR", "NAND", "NOR"
                    int v = e[j];
                    if (f[v] == 0) w[v] &= t;
                    else if (f[v] == 1) w[v] |= t;
                    else if (f[v] == 2) w[v] = !t;
                    else if (f[v] == 3) w[v] ^= t;
                    else if (f[v] == 4) w[v] |= !t;
                    else if (f[v] == 5) w[v] &= !t;
                }
            }
            
            
            // 输出。注意输出根据的是功能器件编号,而建图时节点不是1-n,而是 m+1 - m+n
						// 所以这里输出时要加上 m
            for (auto x : out[k])
            {
                cout << w[m + x] << ' ';
            }
            puts("");
        }
        
    }
    
}
```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值