题解:洛谷 P1118 Backward Digit Sums
【题目来源】
洛谷:P1118 [USACO06FEB] Backward Digit Sums G/S - 洛谷
【题目描述】
FJ 和他的奶牛们喜欢玩一个心算游戏。他们将数字从 \(1\) 到 \(N(1 \le N \le 12)\) 按某种顺序写下来,然后将相邻的数字相加,得到一个数字更少的新列表。他们重复这个过程,直到只剩下一个数字。例如,游戏的一种情况(当 \(N=4\) 时)可能是这样的:
3 1 2 4
4 3 6
7 9
16
在 FJ 背后,奶牛们开始玩一个更难的游戏,她们试图从最终的总和和数字 \(N\) 中确定起始序列。不幸的是,这个游戏有点超出了 FJ 的心算能力。
编写一个程序来帮助 FJ 玩这个游戏,并跟上奶牛们的步伐。
【输入】
共一行两个正整数 \(n,sum\)。
【输出】
输出包括一行,为字典序最小的那个答案。
当无解的时候,请什么也不输出。
【输入样例】
4 16
【输出样例】
3 1 2 4
【核心思想】
-
问题分析:给定 \(N\) 和 \(sum\),需要找到一个 \(1\) 到 \(N\) 的排列,使得按照"相邻数字逐层相加"规则(类似杨辉三角的逐层求和)最终得到的数字等于 \(sum\)。例如 \(N=4\) 时,排列 \((3,1,2,4)\) 的逐层求和过程为:
3 1 2 4 4 3 6 7 9 16这是一个DFS全排列搜索 + 剪枝优化问题,关键在于利用逐层求和过程中 \(b[1]\) 单调递增的特性进行提前剪枝。
-
算法选择:
- DFS全排列枚举:用
book数组标记已使用的数字,递归生成 \(1\) 到 \(N\) 的所有排列 - 逐层求和验证:
calc()函数模拟题目描述的相邻相加过程,计算最终值 - 前缀剪枝:在
calc()中,若某层求和后 \(b[1] > sum\),立即返回 \(-1\),避免无效搜索
- DFS全排列枚举:用
-
关键步骤:
- DFS搜索(当前步数 \(step\)):
- 剪枝验证:调用
calc()计算当前部分排列的逐层和,若返回 \(-1\)(已超 \(sum\))则直接返回 - 终止条件:若 \(step > N\) 且
calc() == sum,输出当前排列并结束程序 - 枚举尝试:遍历 \(i\) 从 \(1\) 到 \(N\),若 \(book[i] = 0\):
- \(a[step] = i\),\(book[i] = 1\)
- 递归 \(dfs(step+1)\)
- 回溯:\(a[step] = 0\),\(book[i] = 0\)
- 剪枝验证:调用
- 逐层求和计算(
calc()):- 将 \(a\) 拷贝到 \(c\)
- 进行 \(N-1\) 轮相邻相加:\(b[j] = c[j] + c[j+1]\)
- 每轮若 \(b[1] > sum\) 返回 \(-1\)
- 将 \(b\) 拷贝回 \(c\) 继续下一轮
- 返回最终 \(c[1]\)
- DFS搜索(当前步数 \(step\)):
-
时间/空间复杂度:
- 时间复杂度:\(O(N! \cdot N^2)\),最坏枚举 \(N!\) 个排列,每个排列验证 \(O(N^2)\)
- 空间复杂度:\(O(N)\),排列数组、标记数组及临时数组
-
DFS剪枝的核心思想:
- 杨辉三角系数:最终和 \(= \sum_{i=1}^{N} a_i \times C(N-1, i-1)\),即每个位置 \(i\) 的贡献系数为组合数。但代码采用直接模拟,更直观
- 前缀单调性剪枝:逐层求和过程中,\(b[1]\) 是 \(a\) 中前若干元素的加权和,随着排列增长单调不减(对于正数),一旦超过 \(sum\) 可立即剪枝
- 字典序最小保证:DFS按 \(1\) 到 \(N\) 的顺序枚举,首次找到的合法解即为字典序最小解
- 全排列模板:标准的
book标记 + 回溯框架,适用于 \(N \le 12\) 的小规模排列搜索 - 适用于"排列约束满足 + 可验证目标值"类问题,核心是利用目标函数的单调性进行有效剪枝
【解题思路】

【算法标签】
普及 #DFS-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, sum, a[15], b[15], c[15], book[15];
int calc() // 按照游戏规则计算
{
for (int i=1; i<=n; i++) { // 将a数组拷贝至c数组
c[i] = a[i];
}
for (int i=1; i<n; i++) { // 依次遍历c数组中所有数
for (int j=1; j<n; j++) { // 相邻两个数相加,并赋值给b数组
b[j] = c[j]+c[j+1];
}
if (b[1]>sum) { // 如果计算后b[1]已经大于sum,则无需继续计算,返回-1
return -1;
}
for (int j=1; j<=n; j++) { // 将b数组拷贝至c数组,进行下一轮计算
c[j] = b[j];
}
}
return c[1]; // 返回c数组第1个元素的值
}
void dfs(int step)
{
int tmp = calc(); // 剪枝,每次都计算一下
if (tmp==-1) return; // 如果c[1]已经超过sum,后面就不用算了
if (step>n) { // 搜索退出条件
if (tmp==sum) { // 如果等于sum
for (int i=1; i<=n; i++) { // 则输出a数组
cout << a[i] << " ";
}
cout << endl;
exit(0); // 并退出程序
}
return; // 如果不等于,即小于,还要继续搜索
}
for (int i=1; i<=n; i++) { // 全排列模板
if (book[i]==0) { // 如果某个数没有被用过
a[step]=i; // 就用这个数
book[i]=1; // 并标记用过
dfs(step+1); // 进行下一次搜索
a[step]=0; // 还原现场
book[i]=0;
}
}
}
int main()
{
cin >> n >> sum; // 输入n和sum
dfs(1); // 进行dfs深搜
return 0;
}
【运行结果】
4 16
3 1 2 4
浙公网安备 33010602011771号