蓝桥杯——Sticks (C++)
题目来源:蓝桥杯算法训练
知识点:搜索
问题描述
George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.
输入格式
The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.
输出格式
The output should contains the smallest possible length of original sticks, one per line.
样例输入
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
样例输出
6
5
题目分析
本题的要求是:将多根短木棍合并为若干个等长的长木棍,且长木棍的长度要最小。
这里是我的一点心得:关于数据问题大概率是从数据的特殊值出发去考虑的,如最值、均值、和、差、乘积等等。通过思考,我们可以发现这道题与和有关系。
虽然不知道原来等长的木棍的长度,但是它们长度的和是可以知道的,就是所有木棍长度的总和。那么,原来的木棍的长度乘上木棍的数量就恰好等于长度之和。因此,可以从和的因数下手,最小的且符合要求的因数就是所求。
在给出的所有短木棍中,最大值有可能就是和的因数,且就是最小的原长度。因此,对和的因数的搜索范围可以定位为:短木棍最大值 ~ 和的一半。对于只有一根木棍的情况比较简单,可以单独处理。对因数从小到大进行搜索,一旦当前的因数能由短木棍构成,且是多个等长的,那么我们就找到了答案。
对输入的长度从大到小进行排序。在搜索时从头到尾取木棍进行拼接,这里有几种需要剪枝(即停止当前搜索)的情况:
- 当前这根棍子用过了。
- 当前这根棍子跟上一根长度相同,但是上一根没用过(因为排序了,所以可以这样)。
- 当前已经拼出了一根按照要求长度的木棍,但是后面剩下的棍子拼不出这种长度的木棍。
- 拼接当前的棍子,长度还不够要求,但是所剩的棍子拼不出要求的长度。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 65;
int len[N];
int vis[N];
int n, flag, sum, num;
bool cmp(int a, int b) {
if(a > b) return true;
else return false;
}
bool dfs(int s, int cur, int cnt, int k) {
//s:当前已拼凑的长度, cur:开始位置, cnt:已拼凑的数目, k:单根木棍长度
if(cnt == num) {
//拼凑成功
return true;
}
for(int i=cur; i<n; i++) {
if(vis[i] || (i && len[i] == len[i-1] && !vis[i-1])) {
//使用过 或者 与上一根没用上的棍子长度相同,剪枝
continue;
}
if(len[i] + s == k) {
//拼出一根
vis[i] = 1;
if(dfs(0, 0, cnt + 1, k)) {
//继续拼下一根,且成功返回
return true;
}
//后面剩余的棍子无法拼成长度为 k的木棍,提前结束
vis[i] = 0;
return false;
}
if(len[i] + s < k) {
//没拼完
vis[i] = 1;
if(dfs(len[i]+s, i+1, cnt, k)) {
//能成功拼完
return true;
}
vis[i] = 0;
//无法拼成
if(!s) return false;
}
}
return false;
}
int main() {
while(cin >> n && n != 0) {
sum = 0;
for(int i=0; i<n; i++) {
cin >> len[i];
sum += len[i];
}
//从大到小排序
sort(len, len + n, cmp);
//开始处理
flag = 0; //有解标志
// 长度从小到大检查
for(int i=len[0]; i<=sum/2; i++) {
if(sum % i == 0) {
memset(vis, 0, sizeof(vis));
num = sum / i; //棍子数量
if(dfs(0, 0, 0, i)) {
cout << i << endl;
flag = 1;
break;
}
}
}
if(!flag) cout << sum << endl;
}
return 0;
}