Reverse Engineering

GitHub跳转原题关系图返回列表

反复寻找输出一致的单变量条件并删除对应样本,用约束消除判断是否能构造决策列表。

OJ: usaco

题目 ID: 1253

难度:普及-

标签:逻辑推理构造枚举usaco

日期: 2026-07-11 17:21

题意

给出若干条样本,每条样本是一个长度为 N 的 01 输入串,以及对应输出 0/10/1

要判断是否存在一个由 if/elseif/elseif / else if / else 组成的程序,使它对所有样本的输出都一致。

每条 if 语句最多只检查一个变量是否等于 01,然后直接返回 01

思路

先看一个小数据暴力:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 17:21
 * update_at: 2026-07-11 17:23
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXM = 105;
const int MAXC = 15;

int n, m;
string input_value[MAXM];
char output_value[MAXM];

int cond_cnt;
int rule_cnt;
int rule_bit[MAXC], rule_val[MAXC], rule_ret[MAXC];
bool used_cond[MAXC];
bool found_program;

int run_program(string s, int default_ret) {
    for (int i = 1; i <= rule_cnt; i++) {
        if (s[rule_bit[i]] == char('0' + rule_val[i])) {
            return rule_ret[i];
        }
    }
    return default_ret;
}

bool current_program_matches(int default_ret) {
    for (int i = 1; i <= m; i++) {
        int ret = run_program(input_value[i], default_ret);
        if (ret != output_value[i] - '0') {
            return false;
        }
    }
    return true;
}

void dfs_program() {
    if (found_program) {
        return;
    }

    if (current_program_matches(0) || current_program_matches(1)) {
        found_program = true;
        return;
    }

    if (rule_cnt == cond_cnt) {
        return;
    }

    // 枚举下一条 if 语句:检查某个变量是否等于 0/1,并返回 0/1。
    for (int c = 0; c < cond_cnt; c++) {
        if (used_cond[c]) {
            continue;
        }
        used_cond[c] = true;

        for (int ret = 0; ret <= 1; ret++) {
            rule_cnt++;
            rule_bit[rule_cnt] = c / 2;
            rule_val[rule_cnt] = c % 2;
            rule_ret[rule_cnt] = ret;

            dfs_program();

            rule_cnt--;
            if (found_program) {
                break;
            }
        }

        used_cond[c] = false;
        if (found_program) {
            break;
        }
    }
}

bool solve_case() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> input_value[i] >> output_value[i];
    }

    cond_cnt = 2 * n;
    rule_cnt = 0;
    found_program = false;
    for (int i = 0; i < cond_cnt; i++) {
        used_cond[i] = false;
    }

    dfs_program();
    return found_program;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        if (solve_case()) {
            cout << "OK\n";
        } else {
            cout << "LIE\n";
        }
    }

    return 0;
}

这个暴力枚举所有可能的 decision list:每条语句检查哪个变量、检查哪个值、返回什么,以及最后默认返回什么。它能直接验证小数据,但程序空间太大,不能用于满分数据。

满分做法反过来构造程序。

如果存在一条分支:

text
if (b[bit] == val) return y;

那么所有满足 b[bit] == val 的剩余样本,输出必须都等于 y

反过来,如果我们找到某个 bit, val,使得所有满足它的剩余样本输出都相同,那么就可以把它作为一条合法分支,并把这些样本删除。

于是算法是:

  1. 维护哪些样本已经被解释;
  2. 枚举所有 bitval
  3. 若当前条件覆盖到的剩余样本输出全相同,就删除这些样本;
  4. 重复这个过程。

如果最后所有样本都被删除,说明可以按删除顺序写出一个合法程序,输出 OK。如果某一轮无法删除任何样本,但还有样本剩下,就输出 LIE

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 17:21
 * update_at: 2026-07-11 17:23
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXM = 105;
const int MAXN = 105;

int n, m;
string input_value[MAXM];
char output_value[MAXM];
bool removed[MAXM];

bool solve_case() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> input_value[i] >> output_value[i];
        removed[i] = false;
    }

    while (true) {
        bool found = false;

        for (int bit = 0; bit < n && !found; bit++) {
            for (char val = '0'; val <= '1' && !found; val++) {
                bool has_input = false;
                bool ok = true;
                char same_output = '?';

                for (int i = 1; i <= m; i++) {
                    if (removed[i]) {
                        continue;
                    }
                    if (input_value[i][bit] != val) {
                        continue;
                    }

                    if (!has_input) {
                        has_input = true;
                        same_output = output_value[i];
                    } else if (same_output != output_value[i]) {
                        ok = false;
                    }
                }

                if (has_input && ok) {
                    found = true;
                    for (int i = 1; i <= m; i++) {
                        if (!removed[i] && input_value[i][bit] == val) {
                            removed[i] = true;
                        }
                    }
                }
            }
        }

        if (!found) {
            break;
        }
    }

    for (int i = 1; i <= m; i++) {
        if (!removed[i]) {
            return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        if (solve_case()) {
            cout << "OK\n";
        } else {
            cout << "LIE\n";
        }
    }

    return 0;
}

复杂度

最多删除 M 轮。每轮枚举 2N 个条件,每个条件扫描 M 条样本。

时间复杂度为 O(NM2)O(NM^2),空间复杂度为 O(NM)O(NM)

总结

本题的核心不是模拟代码执行,而是寻找可以安全写成 if 分支的条件。

只要某个单变量条件覆盖的剩余样本输出一致,就能删除这批样本。这个过程能删完就是 OK,卡住就是 LIE