西工大 poj sticks

本文介绍了一道经典的深搜题目——西工大POJ sticks,讲解了解题思路和剪枝技巧。通过由大到小的排序、利用排序剪枝、避免重复搜索等方法,实现原棒最小可能长度的搜索。文章详细阐述了搜索顺序、递归参数的传递、状态判断等方面的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么都不说先上代码



#include <iostream>

int n;              //小棒总数
int len;            //当前枚举的原棒长度
int parts;          //当前组合的原棒数
int max;            //最长小棒的长度
int sum;            //所有小棒的总长
int tail;           //指向尾部长度较小的棒
int l[64][2];       //存储小棒相关信息
bool used[64];      //标记小棒是否使用

void sort() {
//对小棒进行排序
for (int i=0;i<n;i++)
{
for (int j=i+1;j<n;j++)
{
if (l[0]<l[j][0])
{
int t=l[0];
l[0]=l[j][0];
l[j][0]=t;
}
}
}
//给每一个小棒加上指针,指向下一个长度不同的小棒
int i=0;
while (i<n)
{
int j=i+1;
while (j<n&&l[j][0]==l[0]) j++;
for (int k=i;k<j;k++) l[k][1]=j;
i=j;
}
}

void initial() {
//初始化设置所有小棒均未使用
for (int i=0;i<n;i++)
{
used=false;
}
int sumr=0;
//计算一个指针,其后小棒相加之和大约为一根原棒长度
int i=n-1;
for (int i=n-1;i>=0;i--)
{
sumr+=l[0];
if (sumr>len) break;
}
tail=i;
}

inline int sumres(int m) {
//计算下标大于等于m且未使用的小棒总长之和
int sumr=0;
for (int i=m;i<n;i++) {
if (used==false)
sumr+=l[0];
}
return sumr;
}

bool search(int res,int next,int cpl)
//策略2:用next存储当前可用的下一支小棒
{
//修正参数,当res=len时表示正好拼接成一支原棒
if (res==len) {
res=0;
next=1;
cpl++;
}
//当已拼出的原棒为应拼出原棒时,返回TRUE
if (cpl==parts)
return true;
int i=next;
while (i<n) {
//尝试未使用的小棒
if (used==false) {
//判断接入此小棒是否超出原棒长度
if (res+l[0]<=len) {
//将该小棒标为已使用
used=true;
//递归搜索,一旦成功则不断向上一级返回TRUE
if (search(res+l[0],i+1,cpl)) return true;
//还原该小棒为未使用
used=false;
//策略4:第一次拼接当前原棒无解则当前状态必定无解,跳出
if (res==0) break;
//策略5:若尝试一根小棒恰能组合出当前原棒的剩余长度的小棒失败,则不再尝试比它更小的小棒
if (res+l[0]==len) break;
}
//策略3:若使用当前小棒无解则尝试下一支长度不同的小棒
i=l[1];
//策略6:计算当前所剩可用小棒是否足以拼出当前这支木棒原型
if (i>=tail&&sumres(i)<len-res) break;
continue;
}
i++;
}
return false;
}

int main() {
while (scanf("%d",&n),n!=0) {
//输入
sum=0;
for (int i=0;i<n;i++) {
scanf("%d",&l[0]);
sum+=l[0];
}
//策略1:按小棒长度排序
sort();
max=l[0][0];
for (len=max;len<=sum;len++) {
//枚举原棒长度,并判断其能否整除总长度
if (sum%len==0) {
parts=sum/len;
initial();
if (search(0,0,0)) {
printf("%d\n",len);
break;
}
}
}
}
return 0;
}



【题意描述】
给出N根小木棒(以下称小棒)的长度Li,已知这N根小木棒原本由若干根长度相同的长木棒(以下称原棒)分解而来。要求出原棒的最小可能长度。

【数据范围】
木棒数N<=64
任意小棒长度Li<=50

【题目类型】
这题在网络上被称为经典的深搜题,其中用到的搜索方法和剪枝技巧十分经典。就我做过这题之后的感受来看,的确如此。其中的许多技巧效果非常显著而且在其它 搜索题中也经常用到。另外建议大家在做搜索题的时候加上时间测试,以便在调程序的时候观察和比较各项剪枝带来的效率提升。

【解题思路】
由小到大枚举所有可能的原棒长度,通过深度优先搜索尝试小棒能否组合成原棒,一旦检验成功则算法结束,当前原棒长度即为最小可能原棒长度。
枚举过程如下,设小棒的总长为SUM,最长小棒长度为MAX,从MAX开始由小到大枚举原棒长度LEN,使得LEN能被SUM整除。然后进行搜索,尝试用所有小棒拼出SUM/LEN根的原棒。
搜 索过程如下,首先用一数组标USED[]记某一小棒在当前状态下是否已经被用于组合原棒,另有有两个主要参数表示搜索时的状态,CPL表示已经组合好的原 棒数,RES表示当前正在组合的原棒(以下称当前原棒)已组合出的长度。在每一种状态下,尝试所有可能拼接在当前原棒上的未使用的小棒,即将满足 USED=FALSE且RES+Li<=LEN的小棒接入当前原棒,传递RES的参数RES+Li,若RES+Li=LEN,传递CPL的参数 CPL+1,否则,传递CPL,同时令USED=TRUE,然后进行递归,进入下一层搜索。退出下层递归后,将USED重新赋为FALSE。当CPL= SUM/LEN时,返回TRUE,表示搜索成功,一旦下一层递归返回TRUE,当前递归也返回TRUE,不断返回,直到跳出函数调用,表示当前原棒长度为 可行解,且为最小,输出。
本题的难点在于搜索的方法和剪枝的技巧。本题中用到的主要技巧有:
1. 搜索顺序。首先依据小棒长度进行由大到小的排序,在每一层搜索时首先将长度大的小棒填入当前原棒中。因为当相对长的小棒占据了原棒的大部分空间后能大大减小可行的搜索状态。
2. 利用排序剪枝。在组合同一支原棒的时候,由于检验小棒是否可用的顺序也是由大到小的,因此在检验到一支小棒可用时,如果当前棒还合填满,可能填入当前棒的 小棒的长度也不会比现在填入的这支小棒长。因此,增加一个递归参数NEXT表示可能用于组合当前棒的第一支小棒的数组下标。参数传递时,若当前正好拼成一 支原棒,NEXT还原回1,否则将NEXT+1传递给下一层递归。
3. 不进行重复搜索。即在某一状态,若将某一长度的小棒填入当前原棒进行搜索无法最终拼出所有原棒,则对于当前状态,相同长度的小棒也无法填入当前原棒而得到 最终解。因此,在记录小棒长度的数组L中增加一指针用于指向下一个与之长度不同的小棒的数组下标,则搜索时,若某一长度小棒不成功,直接尝试下一个与之长 度不同的小棒。
4. 首次只尝试最长的小棒。在第一次组合拼接某一根原棒时,首先放入的是当前最长的小棒,并且,如果当前状态可以完成组合,则该小棒必定要放入之后的某一根原 棒中,即假设它放在当前原棒中,若放入后搜索失败,则当前状态必定不可能成功,需要回溯。因此,在RES=0时,若第一次搜索失败,则不断续当前状态的其 它搜索。
5.如果当前最长的一支可用小棒L'0恰能填满当前正在组合的一支原棒,则如果此次尝试失败,在当前状态下不再做其它尝试,返回上一层递归。因为若当前状 态还有可能成功,则当前原棒的剩余长度必定能由另几支更短的小棒L'1、L'2……L'n组合成,且L'0必定出现在之后组合的某支原棒之中,则可以将其 中的L'0替换为L'1、L'2……L'n,而将L'0移加当前原棒中,则两种状态等价,因此同样必定失败。因此,在RES+Li=LEN时,若搜索失 败,则同样不断续当前状态的其它搜索。
6. 判断所剩可用小棒是否足够拼接当前原棒。累加所有小于当前已经尝试的小棒的长度且未使用的小棒,判断是否足够拼接出当前原棒,若不能,则不继续当前搜索。该剪枝效果不很明显,且计算位置放置不佳可能反而降低率效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值