题解:洛谷 P9947 Photoshoot

【题目来源】

洛谷:P9947 [USACO20JAN] Photoshoot B - 洛谷

【题目描述】

Farmer John 在给他编号为 \(1\dots N\)\(N\)头奶牛排队拍照\((2\le N\le 10^3)\)。FJ 一开始计划从左向右数第i个位置排编号为 \(a_i\)的奶牛,他在一张纸上写下了排列 \(a_1,a_2,\dots,a_N\)。不幸的是,这张纸刚刚被 Farmer Nhoj 偷走了!

幸好 FJ 仍然有机会恢复他之前写下的排列。在这张纸被偷走之前,Bessie 记录了序列 \(b_1,b_2,\dots,b_{N-1}\),对于每一个\(1\le i\lt N\)满足\(b_i=a_i+a_{i+1}\)

基于 Bessie 的信息,帮助 FJ 恢复可以产生序列 \(b\) 的“字典序最小”的排列 \(a\)。排列 \(x\) 字典序小于排列 \(y\),如果对于某个 \(j\),对于所有 \(i\lt j\) 均有 \(x_i=y_i\),且有 \(x_j\lt y_j\)(换句话说,这两个排列到某个位置之前都相同,在这个位置上 \(x\)小于 \(y\))。保证存在至少一个满足条件的 \(a\)

【输入】

输入的第一行包含一个整数 \(N\)

第二行包含 \(N-1\)个空格分隔的整数 \(b_1,b_2,\dots,b_{N-1}\)

【输出】

输出一行,包含 \(N\)个空格分隔的整数 \(a_1,a_2,\dots,a_N\)

【输入样例】

5
4 6 7 6

【输出样例】

3 1 5 2 4

【核心思想】

  1. 问题分析:给定 \(N-1\) 个相邻奶牛编号之和 \(b_i = a_i + a_{i+1}\),求字典序最小的排列 \(a_1, \dots, a_N\)\(1 \leq a_i \leq N\),互不相同)。本质上是枚举验证问题:枚举第一个元素 \(a_1\),递推计算后续元素并验证合法性,第一个合法的即为字典序最小。

  2. 算法选择

    • 枚举 \(a_1\):从 \(1\)\(N\) 枚举 \(a_1\),利用 \(a_{i+1} = b_i - a_i\) 递推
    • 合法性验证:检查所有 \(a_i \in [1, N]\) 且互不相同
  3. 关键步骤

    • 读取数据:读入 \(N\)\(b[1..N-1]\)
    • 枚举 \(a_1\)\(i = 1\)\(N\)):
      • \(a[1] = i\)
      • 递推计算 \(a[j+1] = b[j] - a[j]\)\(j = 1\)\(N-1\)
      • 验证合法性
        • 所有 \(a[j] \in [1, N]\)
        • 所有 \(a[j]\) 互不相同(用 usedst 数组标记)
      • 若合法,输出 \(a[1..N]\) 并结束
    • 输出结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2)\),枚举 \(N\)\(a_1\),每次验证 \(O(N)\)
    • 空间复杂度:\(O(N)\),数组存储
  5. 枚举验证的核心思想

    • 递推的确定性:一旦 \(a_1\) 确定,整个序列唯一确定(\(a_{i+1} = b_i - a_i\)),无需搜索
    • 字典序最小策略\(a_1\) 从小到大枚举,第一个合法解即为字典序最小
    • 合法性约束\(a_i\) 必须是 \(1\)\(N\) 的排列,因此需要检查值域和互异性
    • 与解方程的联系\(b_i = a_i + a_{i+1}\) 构成线性方程组,但整数约束和排列约束使问题非平凡
    • 适用于排列还原、递推验证、字典序优化类问题

【解题思路】

【解题思路2】

1.穷举对象:只要枚举第一个\(a_1\)\(a_2=b_1-a_1\)\(a_3=b_2-a_2\)\(a_n=b_{n-1}-a_{n-1}\)

\(b_1=a_1+a_2\)

\(b_2=a_2+a_3\)

\(b_3=a_3+a_4\)

2.字典序最小,第一个\(a_1\)最小就可以

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, a[1005], b[1005], used[1005];  // 定义used数组判断数据是否重复
// ifstream filein("photo.in");
// ofstream fileout("photo.out");
int main()
{
    // filein >> n;  // 输入n
    cin >> n;  // 输入n
    for (int i=1; i<n; i++) {  // 依次输入b数组的值
        // filein >> b[i];
        cin >> b[i];
    }

    for (int i=1; i<b[1]; i++) {  // a1从1开始遍历,直至b[1],因为a2>0
        a[1] = i;  // 设置a[1]为i
        for (int j=1; j<n; j++) {  // 进行n-1次计算
            a[j+1] = b[j] - a[j];  // 得到a[2] - a[n]的值
        }
        int mark = 0;  // 定义mark用于判断a数组是否有效
        memset(used, 0, sizeof(used));  // 每次初始化used数组
        for (int i=1; i<=n; i++) {  // 遍历n个值
            if (a[i]<=0 || a[i]>n) {  // 如果a[i]大于0或者大于n
                mark = 1;  // 判断无效
                break;  // 并结束循环
            }
            used[a[i]]++;  // 否则进行统计
        }
        for (int i=1; i<=n; i++) {  // 再次遍历n个值
            if (used[i]>1) mark = 1;  // 判断是否有数据重复
        }
        if (mark==1) continue;  // 遇到不正确的,i递增,进行下一次查找
        else break;  // 找到第一个满足所有条件的就退出,此时a数据为字典序最小的结果
    }
    for (int i=1; i<n; i++) {  // 打印a数组
        // fileout << a[i] << " ";
        cout << a[i] << " ";
    }
    // fileout << a[n];  // USACO判断最后一个数字之后不能有空格,所以单独处理a[n]
    cout << a[n];  // USACO判断最后一个数字之后不能有空格,所以单独处理a[n]

    return 0;
}
#include <bits/stdc++.h>  // 包含标准库头文件(万能头文件)
using namespace std;      // 使用标准命名空间

const int N = 1000 + 5;   // 定义常量:最大奶牛数量
int a[N], b[N], n;        // 定义数组:a[i]表示第i个位置的奶牛编号,b数组存储输入数据
bool st[N];               // 定义数组:标记奶牛是否已被选中

/**
 * 检查给定的第一头奶牛编号是否能构成合法排列
 * @param a1 第一头奶牛的编号
 * @return 是否能构成合法排列
 */
bool check(int a1)
{
    memset(st, false, sizeof(st));  // 初始化标记数组
    st[a1] = true;                  // 标记第一头奶牛已被选中
    a[1] = a1;                      // 设置第一头奶牛的编号

    // 计算后续位置的奶牛编号
    for (int i = 2; i <= n; i++)
    {
        a[i] = b[i - 1] - a[i - 1];  // 根据规则计算当前奶牛编号

        // 检查编号是否合法
        if (a[i] < 1 || a[i] > n)
            return false;

        // 检查编号是否重复
        if (st[a[i]])
            return false;

        st[a[i]] = true;  // 标记当前奶牛已被选中
    }

    return true;  // 所有检查通过,返回true
}

int main()
{
    cin >> n;  // 输入奶牛数量

    // 输入b数组数据
    for (int i = 1; i < n; i++)
        cin >> b[i];

    // 枚举第一头奶牛的所有可能编号
    for (int i = 1; i <= n; i++)
    {
        if (check(i))  // 检查当前编号是否能构成合法排列
        {
            // 输出合法排列
            for (int j = 1; j <= n; j++)
                cout << a[j] << ' ';
            break;  // 找到解后立即退出
        }
    }

    return 0;  // 程序正常结束
}

【运行结果】

5
4 6 7 6
3 1 5 2 4
posted @ 2026-08-17 22:49  团爸讲算法  阅读(9)  评论(0)    收藏  举报