题解:洛谷 P8899 Reverse Engineering

【题目来源】

洛谷:P8899 [USACO22DEC] Reverse Engineering B - 洛谷

【题目描述】

Elsie 有一个程序,接受一个 \(N(1\le N\le 100)\) 个变量的数组 \(b[0],\dots,b[N-1]\)作为输入,其中每个变量等于\(0\)\(1\),并且返回对输入数组应用一系列 if / else if / else 语句的结果。每个语句检查至多一个输入变量的值,并返回 \(0\)\(1\)。这类程序的一个例子是:

if (b[1] == 1) return 1;
else if (b[0] == 0) return 0;
else return 1;

例如,如果上方程序的输入是 "10"(即 \(b[0]=1\)\(b[1]=0\)),那么输出应当为 \(1\)

Elsie 告诉了 Bessie 对于 \(M(1\le M\le 100)\)个不同输入的正确输出。Bessie 现在正试图对 Elsie 的程序进行逆向工程。不幸的是,Elsie 可能说了谎;可能不存在上述形式的程序行为与 Elsie 所说的均一致。

对于 \(T(1\le T\le 10)\)个子测试用例中的每一个,判断 Elsie 是否一定在说谎。

【输入】

输入的第一行包含\(T\),为子测试用例的数量。

每一个子测试用例的第一行包含两个整数 \(N\)\(M\),以下 \(M\)行,每行包含一个由 \(N\)\(0\)\(1\) 组成的字符串,表示一个输入(即 \(b[0]\dots b[N-1]\) 的值),以及另一个字符(\(0\)\(1\))表示输出。相邻的子测试用例之间用空行分隔。

【输出】

对于每一个子测试用例,输出一行,包含 OK 或 LIE,分别表示 Elsie 可能没有说谎或是一定在说谎。

【输入样例】

4

1 3
0 0
0 0
1 1

2 4
00 0
01 1
10 1
11 1

1 2
0 1
0 0

2 4
00 0
01 1
10 1
11 0

【输出样例】

OK
OK
LIE
LIE

【核心思想】

  1. 问题分析:给定 \(N\) 个二进制变量和 \(M\) 组输入-输出对,判断是否存在一个由 if/else if/else 构成的程序(每个条件只检查一个变量),使得所有 \(M\) 组数据的行为一致。本质是一个约束一致性检查问题——判断输入-输出对能否被单变量条件链覆盖。

  2. 算法选择

    • 多轮列冲突检测 + 行删除:模拟 if/else if/else 的执行流程,每轮检查每一列(变量):若该列所有值为 \(0\) 的行输出一致,则这些行可被该变量的 == 0 条件覆盖并删除;同理检查值为 \(1\) 的行。重复 \(N\) 轮,若仍有未删除行则存在冲突。
  3. 关键步骤

    • 初始化:读取 \(N, M\)\(M\) 组输入字符串 \(s[i]\) 及输出 \(res[i]\),初始化删除标记 \(\text{del}[i] = \text{false}\)
    • 多轮检测(最多 \(N\) 轮,每轮遍历 \(N\) 列):
      • 对每列 \(i\)(变量 \(b[i]\)):
        • 在未删除的行中,分别检查该列为 \(0\) 和为 \(1\) 的行的输出是否各自一致
        • 记录 \(\text{last0}\)(最近一次 \(0\) 的输出)和 \(\text{last1}\)(最近一次 \(1\) 的输出)
        • 若出现不一致,标记 \(\text{conflict0} = \text{true}\)\(\text{conflict1} = \text{true}\)
        • 删除可覆盖行:若 \(0\) 不冲突,删除所有该列为 \(0\) 的行;若 \(1\) 不冲突,删除所有该列为 \(1\) 的行
    • 判定结果:若所有行都被删除(\(\text{del}[i] = \text{true}\) 对所有 \(i\)),输出 OK;否则输出 LIE
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2 \cdot M)\),最多 \(N\) 轮,每轮 \(N\) 列,每列扫描 \(M\)
    • 空间复杂度:\(O(N \cdot M)\),存储输入数据
  5. 模拟条件链执行的核心思想

    • if/else if/else 的语义对应:程序按顺序检查条件,满足即返回。每轮检测相当于尝试用某个变量的某个值作为当前 if 条件,覆盖所有具有该特征且输出一致的行
    • 行删除模拟执行:被某条件覆盖的行相当于已被程序处理,后续条件不再考虑这些行(else ifelse 只处理剩余行)
    • 多轮必要性:由于条件链有顺序,可能需要多轮才能确定最优顺序(如先检查 \(b[1]\) 再检查 \(b[0]\)),因此最多需要 \(N\)
    • 冲突判定:若某列中同为 \(0\)(或同为 \(1\))的行有不同的输出,则无法用该变量的该值作为统一条件覆盖,标记冲突
    • 适用于程序逆向工程、约束满足、逻辑一致性验证类问题

【解题思路】

【算法标签】

普及 #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int t;
bool res[105], del[105];  // 定义res数组存放每行数据的输出,del数组存放改行后续是否不再遍历的标志位
string s[105];
int n, m;
int main()
{
    cin >> t;  // 输入t
    while (t--) {  // 遍历t组数据
        cin >> n >> m;  // 输入n和m
        for (int i=1; i<=m; i++) {  // 输入m行输入数据
            cin >> s[i] >> res[i];  // 每行数据包含一个字符串s,和对应结果res
        }
        memset(del, false, sizeof(del));  // 每次检查冲突前先初始化del数组
        for (int k=1; k<=n; k++) {  // 第一轮遍历到n列,可以确定n列是否有冲突,那么n-1列就需要第2轮遍历,所以最多需要n轮(如判断程序时先比较b[1],再比较b[0],则需要第1位判断之后,再进行第0位的判断)
            for (int i=0; i<n; i++) {  // 遍历s字符串的n个字符,即逐列比较
                bool conflict0 = false, conflict1 = false;  // 定义变量表示输入0是否冲突,输入1是否冲突
                int last0 = -1, last1 = -1;  // 记录最近一次输入为0时的输出,以及最近一次输入为1的输出
                for (int j=1; j<=m; j++) {
                    if(del[j]) continue;  // 下一轮判断,可以剔除那些已经删除的行(对于n轮判断也起到过滤作用)
                    bool num = (s[j][i]=='1');  // 轻巧,定义num,讲s[j][i]变向地转为数字
                    if (num) {  // 如果输入为1
                        if (last1==-1) {  // 如果没有比较过
                            last1 = res[j];  // 则输入为1对应的输出为res[j]
                        } else if (last1 != res[j]) {  // 如果比较过,且此次输入对应的输出不等于最近一次比较的输出(即说明冲突)
                            conflict1 = true;  // 标记1冲突了
                        }
                    } else {  // 如果输入为0
                        if (last0==-1) {  // 如果没有比较过
                            last0 = res[j];  // 则输入为0对应的输出为res[j]
                        } else if (last0 != res[j]) {  // 如果比较过,且此次输入对应的输出不等于最近一次比较的输出(即说明冲突)
                            conflict0 = true;  // 标记0冲突了
                        }
                    }
                }
                for (int j=1; j<=m; j++) {  // 遍历m行
                    if (s[j][i]=='0' && !conflict0) {  //对于输入为0且不冲突的行
                        del[j] = true;  // 标记del[j]为true,下次比较直接忽略改行(相当于删除该行)
                    }
                    if (s[j][i]=='1' && !conflict1) {  //对于输入为1且不冲突的行
                        del[j] = true;  // 标记del[j]为true,下次比较直接忽略改行(相当于删除该行)
                    }
                }
            }
        }
        int mark = 0;  // 定义mark标识位,用来确定最后输出,初始为0
        for (int i=1; i<=m; i++) {  // 遍历m行
            if (del[i]==false) {  // 如果有任一一行为false,则说明这行的输入对应的输出有冲突(即为假)
                mark = 1;  // 修改mark为1
                break;  // 退出循环
            }
        }
        if (mark==0) cout << "OK" << endl;  // 如果mark不变,仍为0,说明都是不冲突的(即为真)
        else cout << "LIE" << endl;  // 否则输出LIE
    }
    return 0;
}
#include<iostream>
using namespace std;

int T;

int main()
{
    cin >> T;
    while (T--) {
        int n, m, s[105][105], t[105];  // m次输入,每次输入长度为n的01串,第i次输入的第j个数码为s[i][j],对应的输出为t[i]
        bool f[105] = {false}, OK = true;  // f[i]标记第i行是否已经处理完,OK标记Elsie是否诚实
        cin >> n >> m;
        for (int i=1; i<=m; i++) {
            for (int j=0; j<=n-1; j++) {
                scanf("%1d", &s[i][j]);
            }
            cin >> t[i];
        }
        for (int x=1; x<=n; x++) {  // 枚举第x轮操作
            for (int j=0; j<=n-1; j++) {  // 枚举第j列
                // 在第j列中检查所有的0或所有的1对应的输出是否前全部相同
                int p[2] = {-1, -1};  // p[0]表示第j列所有的0匹配的输出,p[0]=-1初始化为没有匹配;p[1]表示第j列所有的1匹配的输出,p[0]=-1初始化为没有匹配
                bool k[2] = {true, true};  // k[0]表示第j列所有的0匹配的输出是否相同,k[0]=true初始化为全部相同;k[1]表示第j列所有的1匹配的输出是否相同,k[1]=true初始化为全部相同
                for (int i=1; i<=m; i++) {  // 枚举第i行
                    if (f[i]==false) {  // 若第i行没有被处理
                        if (p[s[i][j]]==-1) p[s[i][j]] = t[i];  // 若s[i][j]没有匹配则进行匹配
                        else if (p[s[i][j]]!=t[i]) k[s[i][j]] = false;  // 匹配过则检查上次匹配和这次匹配结果是否相同,不同则标记匹配失败
                    }
                }
                for (int i=1; i<=m; i++) {  // 将匹配成功的行标记为已处理
                    if (k[s[i][j]==true]) f[i] = true;
                }
            }
        }
        for (int i=1; i<=m; i++) {
            if (f[i]==false) OK = false;
        }
        if (OK==true) cout << "OK" << endl;
        else cout << "LIE" << endl;
    }

    return 0;
}

【运行结果】

4

1 3
0 0
0 0
1 1
OK

2 4
00 0
01 1
10 1
11 1
OK

1 2
0 1
0 0
LIE

2 4
00 0
01 1
10 1
11 0
LIE
posted @ 2026-08-29 09:40  团爸讲算法  阅读(8)  评论(0)    收藏  举报