题目大意:
乔治将几根长度一样的木棍随机砍断,得到若干长度随机的小木棍,现在他想把这些小木棍拼回去,但是已经忘记掉原木棍的长度和根数,现在请你编程确定原木棍最短为多少。
现有多个测例,每个测例都给出随机长度小木棍的数量n(n不超过64),并给出每根小木棍的长度(小木棍超度不超过50),要求输出可能的原木棍的最小长度,以n = 0作为输入的结束。
注释代码:
/*
* Problem ID : POJ 1011 Sticks
* Author : Lirx.t.Una
* Language : GCC
* Run Time : 0 ms
* Run Memory : 360 KB
*/
#pragma GCC optimize("O2")
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
//maximum number of sticks
//木棍的最大数量
#define MAXSTKN 64
typedef char BOOL;
short len[MAXSTKN];//每条木棍的长度,从下标0计
BOOL usd[MAXSTKN];//used,表示第i号棍子是否使用过
int
fcmp(const void *a, const void *b) {
return *(short *)b - *(short *)a;
}
//思路:由于每一个目标段中都至少包含一条小木棍
//因此目标段的最小长度至少为小木棍的最大长度
//因此可以先将小木棍按照长度从大到小排序
//然后在贪心选择的基础上DFS
BOOL
dfs( int ncplt, short cl, int cur, int ns, short sl, int n ) {
//completed number,已经拼好的棍子的数量
//current length,目前已经拼凑了多长(是指一个段里)
//current stick,当前正扫描的木棍的编号
//segment number,目标段的个数
//segment length,目标段的长度
//totol stick number,小木棍的总数量
if ( ncplt == ns )//如果已经拼完所有目标段则成功退出
return TRUE;
for ( ; cur < n; cur++ ) {//否则就从当前木棍开始检查
//!!!剪枝1
//如果当前木棍已被使用过了
//或者是和已经拼好的长度相加超过目标段长
//则直接跳过
if ( usd[cur] || cl + len[cur] > sl )
continue;
//否则就可以标记为使用过
usd[cur] = TRUE;
//否则就可以标记为使用过,接下来做相应的检查
//否则就可以标记为使用过,接下来做相应的检查
if ( cl + len[cur] == sl ) {
//则继续搜索
//此时因为cl和len[cur]已经拼好了一个完整的段
//因此进入下一层搜索时拼好的数量就是ncplt+1了
//并且拼好的长度清零
//并且从头开始扫描
if ( dfs( ncplt + 1, 0, 0, ns, sl, n ) )
return TRUE;
//!!!剪枝2
//如果剩下的所有小木棍无法完成目标
//则表示当前方案失败
//因为若不使用len[cur],但是为了完成目标
//接下来必定要找到能和cl组成完整段的小木棍
//即使能找到,那这些和cl组成完整段的小木棍的效果
//和len[cur]是一样的,所以就算这一层中不适用len[cur]
//后面的搜索同样是不成功的
//因此这里需要剪枝,不能往下搜索了
return usd[cur] = FALSE;
}
//接下来就是cl + len[cur] < sl的情况了
//继续下一层搜索,只不过拼好的长度为cl + len[cur]了
//由于当前还没有拼完一段目标段,因此还是ncplt
//并且得从cur + 1的位置继续扫描
//因为len是从大到小排序过的(方便贪心选择)
if ( dfs( ncplt, cl + len[cur], cur + 1, ns, sl, n ) )
return TRUE;
//如果不成功,则有可能替换len[cur]
usd[cur] = FALSE;
//!!!剪枝3
//如果cl = 0,则表示拿len[cur]和其它剩下的木棍凑,凑不出目标
//因此只能失败退出
if ( !cl )
return FALSE;
//!!!剪枝4
//否则就表示cl和len[cur]组合在一起是不能和剩下的小木棍凑出目标
//这就意味着cl和其它len组合可能凑出目标
//因此尝试换其它len和cl组合
//但是得避免后面重复测试和len[cur]一样长的木棍
while ( cur + 1 < n && len[cur] == len[cur + 1] )
cur++;
}
return FALSE;//所有小木棍都检测完仍然凑不出
}
int
main() {
int n;//木棍数量
short tl;//totol length,小木棍总长
short ns;//number of segment,目标段数量
short sl;//length of segment,目标段长度(sl = tl / ns)
BOOL cd;//can be done,用于标志当前sl能否被成功凑出
int i;
while ( scanf("%d", &n), n ) {
for ( tl = 0, i = 0; i < n; i++ ) {
scanf("%d", len + i);
tl += len[i];
}
qsort(len, n, sizeof(short), &fcmp);
cd = FALSE;//初始化
//!!!剪枝5
//不用sl从len[0]一直++到tl的方式进行搜索
//因为最多只能被分为tl / len[0]段,最少分为1段
//用段数来扫描比直接用段长++扫描的方式少很多判( tl % sl ) == 0的环节
for ( ns = tl / *len; ns >= 2; ns-- )//!!!剪枝6,如果ns = 2也不行,则ns = 1是必然成立的
//因此不用麻烦地对ns = 1也进行搜索了!!!
if ( !( tl % ns ) ) {
memset(usd, FALSE, sizeof(usd));
if ( dfs( 0, 0, 0, ns, sl = tl / ns, n ) ) {
cd = TRUE;
break;
}
}
if ( cd )
printf("%d\n", sl);
else
printf("%d\n", tl);
}
return 0;
}
无注释代码:
#pragma GCC optimize("O2")
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
#define MAXSTKN 64
typedef char BOOL;
short len[MAXSTKN];
BOOL usd[MAXSTKN];
int
fcmp(const void *a, const void *b) {
return *(short *)b - *(short *)a;
}
BOOL
dfs( int ncplt, short cl, int cur, int ns, short sl, int n ) {
if ( ncplt == ns )
return TRUE;
for ( ; cur < n; cur++ ) {
if ( usd[cur] || cl + len[cur] > sl )
continue;
usd[cur] = TRUE;
if ( cl + len[cur] == sl ) {
if ( dfs( ncplt + 1, 0, 0, ns, sl, n ) )
return TRUE;
return usd[cur] = FALSE;
}
if ( dfs( ncplt, cl + len[cur], cur + 1, ns, sl, n ) )
return TRUE;
usd[cur] = FALSE;
if ( !cl )
return FALSE;
while ( cur + 1 < n && len[cur] == len[cur + 1] )
cur++;
}
return FALSE;
}
int
main() {
int n;
short tl;
short ns;
short sl;
BOOL cd;
int i;
while ( scanf("%d", &n), n ) {
for ( tl = 0, i = 0; i < n; i++ ) {
scanf("%d", len + i);
tl += len[i];
}
qsort(len, n, sizeof(short), &fcmp);
cd = FALSE;
for ( ns = tl / *len; ns >= 2; ns-- )
if ( !( tl % ns ) ) {
memset(usd, FALSE, sizeof(usd));
if ( dfs( 0, 0, 0, ns, sl = tl / ns, n ) ) {
cd = TRUE;
break;
}
}
if ( cd )
printf("%d\n", sl);
else
printf("%d\n", tl);
}
return 0;
}
优化:
注释代码:
/*
* Problem ID : POJ 1011 Sticks
* Author : Lirx.t.Una
* Language : C++
* Run Time : 0 ms
* Run Memory : 136 KB
*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define MAXN 64
using namespace std;
char len[MAXN];
bool usd[MAXN];
int n;//小棍个数
int len_seg;//length of each segment,目标段长度
bool
dfs( int n_rst, int seg_len_rst, int cur_i ) {
//number of rest sticks,剩下没有拼完的小棍数
//rest segment length,目标段还有多少没被拼完
//current ith sticks,当前检测到第cur_i号小棍(下标从0开始)
if ( !n_rst && !seg_len_rst ) return true;//全部拼完
if ( !seg_len_rst ) {//小棍没用完,但是当前目标段为0
//表示刚拼完一个目标段,还需用剩下的小棍拼目标端
seg_len_rst = len_seg;//初始化当前目标段待拼的剩余长度
cur_i = 0;//按照目标端从左到右小棍长度降低的规则从头开始检测小棍
}
for ( ; cur_i < n; cur_i++ )
if ( !usd[cur_i] && len[cur_i] <= seg_len_rst ) {//检测的小棍不能大于当前剩余目标段长
usd[cur_i] = true;//符合要求,先试探性使用一下该小棍
//目标段中小棍从左到右长度递减
//因此一下次搜索cur_i必须前进一格
if ( dfs( n_rst - 1, seg_len_rst - len[cur_i], cur_i + 1 ) ) return true;
usd[cur_i] = false;//搜索失败,退还该木棍 //剪枝1
//如果当前小木棍是当前 //如果当前小木棍是当前
//目标段的头 //目标段的尾
//则表示是上一次检测的小木棍有问题,不用继续替换当前小木棍,因此直接失败退出
if ( seg_len_rst == len_seg || seg_len_rst == len[cur_i] ) return false;
//剪枝2 //剪枝3
//虽然当前小木棍既不是当前目标段的头也不是尾
//但反正是失败了,因此在接下来的搜索中排除和它一样长的小木棍
while ( cur_i + 1 < n && len[cur_i] == len[cur_i + 1] ) cur_i++;
} //剪枝4
return false;//一直都没搜出答案,失败退出
}
bool
fcmp( char a, char b ) {
return a > b;
}
int
main() {
int len_tot;//所有小棍总长度
int n_seg;//假设的目标段的个数
int i;
bool done;
while ( scanf("%d", &n), n ) {
len_tot = 0;
for ( i = 0; i < n; i++ ) {
scanf("%d", len + i);
len_tot += len[i];
}
sort(len, len + n, fcmp);
done = false;
for ( n_seg = len_tot / *len; n_seg > 1; n_seg-- )
if ( !( len_tot % n_seg ) ) {
memset(usd, 0, sizeof(usd));
if ( dfs( n, len_seg = len_tot / n_seg, 0 ) ) {
done = true;
break;
}
}
if ( done ) printf("%d\n", len_seg);
else printf("%d\n", len_tot);
}
return 0;
}
无注释代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define MAXN 64
using namespace std;
char len[MAXN];
bool usd[MAXN];
int n;
int len_seg;
bool
dfs( int n_rst, int seg_len_rst, int cur_i ) {
if ( !n_rst && !seg_len_rst ) return true;
if ( !seg_len_rst ) {
seg_len_rst = len_seg;
cur_i = 0;
}
for ( ; cur_i < n; cur_i++ )
if ( !usd[cur_i] && len[cur_i] <= seg_len_rst ) {
usd[cur_i] = true;
if ( dfs( n_rst - 1, seg_len_rst - len[cur_i], cur_i + 1 ) ) return true;
usd[cur_i] = false;
if ( seg_len_rst == len_seg || seg_len_rst == len[cur_i] ) return false;
while ( cur_i + 1 < n && len[cur_i] == len[cur_i + 1] ) cur_i++;
}
return false;
}
bool
fcmp( char a, char b ) {
return a > b;
}
int
main() {
int len_tot;
int n_seg;
int i;
bool done;
while ( scanf("%d", &n), n ) {
len_tot = 0;
for ( i = 0; i < n; i++ ) {
scanf("%d", len + i);
len_tot += len[i];
}
sort(len, len + n, fcmp);
done = false;
for ( n_seg = len_tot / *len; n_seg > 1; n_seg-- )
if ( !( len_tot % n_seg ) ) {
memset(usd, 0, sizeof(usd));
if ( dfs( n, len_seg = len_tot / n_seg, 0 ) ) {
done = true;
break;
}
}
if ( done ) printf("%d\n", len_seg);
else printf("%d\n", len_tot);
}
return 0;
}
单词解释:
randomly:adv, 随机地,任意地