题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个二进制变量和 \(M\) 组输入-输出对,判断是否存在一个由
if/else if/else构成的程序(每个条件只检查一个变量),使得所有 \(M\) 组数据的行为一致。本质是一个约束一致性检查问题——判断输入-输出对能否被单变量条件链覆盖。 -
算法选择:
- 多轮列冲突检测 + 行删除:模拟
if/else if/else的执行流程,每轮检查每一列(变量):若该列所有值为 \(0\) 的行输出一致,则这些行可被该变量的== 0条件覆盖并删除;同理检查值为 \(1\) 的行。重复 \(N\) 轮,若仍有未删除行则存在冲突。
- 多轮列冲突检测 + 行删除:模拟
-
关键步骤:
- 初始化:读取 \(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\) 的行
- 对每列 \(i\)(变量 \(b[i]\)):
- 判定结果:若所有行都被删除(\(\text{del}[i] = \text{true}\) 对所有 \(i\)),输出
OK;否则输出LIE
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2 \cdot M)\),最多 \(N\) 轮,每轮 \(N\) 列,每列扫描 \(M\) 行
- 空间复杂度:\(O(N \cdot M)\),存储输入数据
-
模拟条件链执行的核心思想:
- if/else if/else 的语义对应:程序按顺序检查条件,满足即返回。每轮检测相当于尝试用某个变量的某个值作为当前
if条件,覆盖所有具有该特征且输出一致的行 - 行删除模拟执行:被某条件覆盖的行相当于已被程序处理,后续条件不再考虑这些行(
else if和else只处理剩余行) - 多轮必要性:由于条件链有顺序,可能需要多轮才能确定最优顺序(如先检查 \(b[1]\) 再检查 \(b[0]\)),因此最多需要 \(N\) 轮
- 冲突判定:若某列中同为 \(0\)(或同为 \(1\))的行有不同的输出,则无法用该变量的该值作为统一条件覆盖,标记冲突
- 适用于程序逆向工程、约束满足、逻辑一致性验证类问题
- if/else if/else 的语义对应:程序按顺序检查条件,满足即返回。每轮检测相当于尝试用某个变量的某个值作为当前
【解题思路】

【算法标签】
普及 #模拟
【代码详解】
#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
浙公网安备 33010602011771号