题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N-1\) 个相邻奶牛编号之和 \(b_i = a_i + a_{i+1}\),求字典序最小的排列 \(a_1, \dots, a_N\)(\(1 \leq a_i \leq N\),互不相同)。本质上是枚举验证问题:枚举第一个元素 \(a_1\),递推计算后续元素并验证合法性,第一个合法的即为字典序最小。
-
算法选择:
- 枚举 \(a_1\):从 \(1\) 到 \(N\) 枚举 \(a_1\),利用 \(a_{i+1} = b_i - a_i\) 递推
- 合法性验证:检查所有 \(a_i \in [1, N]\) 且互不相同
-
关键步骤:
- 读取数据:读入 \(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]\) 互不相同(用
used或st数组标记)
- 若合法,输出 \(a[1..N]\) 并结束
- 输出结果
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2)\),枚举 \(N\) 个 \(a_1\),每次验证 \(O(N)\)
- 空间复杂度:\(O(N)\),数组存储
-
枚举验证的核心思想:
- 递推的确定性:一旦 \(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
浙公网安备 33010602011771号