Range Reconstruction

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

从右往左构造数组,每步只尝试相邻全距的正负两种方向,并检查新增左端点区间。

OJ: usaco

题目 ID: 1256

难度:普及+/提高

标签:构造区间枚举usaco

日期: 2026-07-11 21:22

题意

给定一个数组所有区间的全距:

text
r[i][j] = max(a[i..j]) - min(a[i..j])

要求构造任意一个数组,使得它的所有区间全距都和输入一致。

思路

先看小数据暴力:相邻两数的差的绝对值已经确定,枚举每个相邻差值的正负号,生成完整数组后检查。

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 21:22
 * update_at: 2026-07-11 21:24
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 15;

int n;
long long r[MAXN][MAXN];
long long a[MAXN];
bool found;

bool check_all() {
    for (int i = 1; i <= n; i++) {
        long long mn = a[i];
        long long mx = a[i];
        for (int j = i; j <= n; j++) {
            if (mn > a[j]) mn = a[j];
            if (mx < a[j]) mx = a[j];
            if (mx - mn != r[i][j]) {
                return false;
            }
        }
    }
    return true;
}

// 枚举相邻两数差值的正负号,生成完整数组后统一检查。
void dfs_build(int pos) {
    if (found) return;

    if (pos == n + 1) {
        if (check_all()) {
            found = true;
        }
        return;
    }

    long long d = r[pos - 1][pos];
    a[pos] = a[pos - 1] + d;
    dfs_build(pos + 1);
    if (found) return;

    a[pos] = a[pos - 1] - d;
    dfs_build(pos + 1);
}

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

    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) {
            cin >> r[i][j];
        }
    }

    a[1] = 0;
    dfs_build(2);

    for (int i = 1; i <= n; i++) {
        if (i > 1) cout << ' ';
        cout << a[i];
    }
    cout << '\n';

    return 0;
}

暴力的枚举对象是:

text
a[i] = a[i-1] + r[i-1][i]
或
a[i] = a[i-1] - r[i-1][i]

共有 2N12^{N-1} 种,不能直接做。

正解从右往左构造。先令:

text
a[N] = 0

假设 a[i+1..N] 已经构造好。因为:

text
r[i][i+1] = |a[i] - a[i+1]|

所以 a[i] 只有两种可能:

text
a[i] = a[i+1] + r[i][i+1]
a[i] = a[i+1] - r[i][i+1]

加入 a[i] 后,右侧旧区间都不变。新增的约束只来自:

text
[i,i], [i,i+1], ..., [i,N]

所以先尝试第一种,如果所有以 i 为左端点的区间全距都正确,就保留;否则改成第二种。输入保证有解,因此两种方向中至少一种可行。

注意:本题输出不唯一。整体平移或整体取反都不会改变区间全距,所以你的输出不需要和样例完全一样,只要合法即可。

代码

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 21:22
 * update_at: 2026-07-11 21:24
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 305;

int n;
long long r[MAXN][MAXN];
long long ans[MAXN];

bool check_prefix(int left) {
    long long mn = ans[left];
    long long mx = ans[left];

    for (int j = left; j <= n; j++) {
        if (mn > ans[j]) mn = ans[j];
        if (mx < ans[j]) mx = ans[j];
        if (mx - mn != r[left][j]) {
            return false;
        }
    }
    return true;
}

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

    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) {
            cin >> r[i][j];
        }
    }

    ans[n] = 0;
    for (int i = n - 1; i >= 1; i--) {
        ans[i] = ans[i + 1] + r[i][i + 1];
        if (!check_prefix(i)) {
            ans[i] = ans[i + 1] - r[i][i + 1];
        }
    }

    for (int i = 1; i <= n; i++) {
        if (i > 1) cout << ' ';
        cout << ans[i];
    }
    cout << '\n';

    return 0;
}

复杂度

每个左端点检查一遍右端点,总时间复杂度为 O(N2)O(N^2)

存储区间全距矩阵需要 O(N2)O(N^2) 空间。

总结

本题的关键是不要试图恢复原数组本身,而是恢复一个全距相同的数组。

从右往左构造时,每个新数只有正负两个候选;检查新增左端点区间即可把指数枚举降到 O(N2)O(N^2)