您的位置:首页 > 其它

POJ 1011 Sticks

2012-08-14 17:43 127 查看
地址:http://poj.org/problem?id=1011

问题描述:

乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棒的长度都不超过50个长度单位。然后他又想把这些木棒恢复到为裁截前的状态,但忘记了木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棒的长度都用大于零的整数表示

输入:由多个案例组成,每个案例包括两行。第一行是一个不超过64的整数,表示裁截之后共有多少节木棒。第二行是经过裁截后,所得到的各节木棒的长度。在最后一个案例之后,是零。

输出:为每个案例,分别输出木棒的可能最小长度。每个案例占一行。

思路1:

题意: 给你n 木棍碎片,要求还原原木棍,且原木棍等长,问最短长度是多少。

分析: 各种剪枝

/*1:越长的木棍对后面木棍的约束力越大,因此要把小木棍排序,
按木棍长度从大到小搜索,这样就能在尽可能靠近根的地方
剪枝。
:当出现加上某根木棍恰好能填满一根原始木棍,但由在后面的
搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
:考虑每根原始木棍的第一根木棍,如果当前枚举的木棍长度无
法得出合法解,就不必考虑下一根木棍了,当前木棍一定是作
为某根原始木棍的第一根木棍的,现在不行,以后也不可能得
出合法解。也就是说每根原始木棍的第一根小木棍一定要成
功,否则就返回。
:剩下一个通用的剪枝就是跳过重复长度的木棍,当前木棍跟
它后面木棍的无法得出合法解,后面跟它一样长度的木棍也
不可能得到合法解,因为后面相同长度木棍能做到的,前面这
根木棍也能做到。
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int cmp(const void*p1,const void*p2)
{
return *(int*)p2-*(int*)p1;
}
int a[100],v[100];
int n,len,s,tot,min;
bool dfs(int k,int mi,int left)
{
int i;
if(left==min)
return 1;
for(i=k;i<=n;i++)
if(!v[i]&&a[i]<=mi)
{
v[i]=1;
if(a[i]==mi&&dfs(1,len,left-a[i]))
return 1;
else if(dfs(i+1,mi-a[i],left-a[i]))
return 1;
v[i]=0;
if(a[i]==min)return 0;
if(left==tot)return 0;
if(mi==len)  return 0;
while(a[i+1]==a[i])
i++;
}
return 0;
}
int main()
{
int i,res;
while(scanf("%d",&n),n)
{
tot=0;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
tot+=a[i];
}
qsort(a+1,n,sizeof(a[0]),cmp);
len=a[1];
res=len;
memset(v,0,sizeof(v));
res=tot;
for(;len<tot;len++)
if(tot%len==0&&dfs(1,len,tot))
{
res=len;
break;
}
printf("%d\n",res);
}
return 0;
}


思路2:

思想很简单,一个接一个的把木棍拼起来,最后把木棍用光。
* 关键的地方是几个剪枝技巧:
* 设所有木棍的总长度为 Sum, 最终的答案(长度)是 L。
* 1. 首先要明白, Sum一定要能被 L 整除。
* 2. L 一定 大于等于 题目给出的最长的木棍的长度 Max。
* 由上述两点,我们想到,可以从 Max 开始递增地枚举 L,
* 直到成功地拼出 Sum/L 支长度为 L 的木棍。
* 搜索种的剪枝技巧:
* 3. 将输入的输入从大到小排序,这么做是因为一支长度为 K
* 的完整木棍,总比几支短的小木棍拼成的要好。
* 形象一些:
* 如果我要拼 2 支长为8的木棍,第一支木棍我拼成
* 5 + 3
* 然后拼第二支木棍但是失败了,而我手中还有长为 2 和 1
* 的木棍,我可以用 5 + 2 + 1 拼好第一支,再尝试拼第二
* 支,仔细想一想,就会发现这样做没意义,注定要失败的。
* 我们应该留下 2+1 因为 2+1 比 3 更灵活。
* 4. 相同长度的木棍不要搜索多次, 比如:
* 我手中有一些木棍, 其中有 2 根长为 4 的木棍, 当前搜索
* 状态是 5+4+.... (即表示长度为 5,4,2 的三支拼在一起,
* ...表示深层的即将搜索的部分), 进行深搜后不成功,故我
* 没必要用另一个 4 在进行 5+4+...
* 5. 将开始搜索一支长为 L 的木棍时,我们总是以当前最长的未
* 被使用的 木棍开始,如果搜索不成功,那么以比它短的开始
* 那么也一定不能取得全局的成功。因为每一支题目给出的木棍
* 都要被用到。
* 如果,有
* 4
* 5 4 4 3 2
* 想拼成长为 6 的木棍,那么从 5 开始, 但是显然没有能与 5
* 一起拼成 6 的,那么我就没必要去尝试从 4 开始的,因为
* 最终 5 一定会被遗弃。在拼第 2 3 ... 支木棍时,一样。
* 6. 最后的最简单的一个就是,
* for(int i = 0; i < n; i++)
* for(int j = 0; j < n; j++)
* {}
* 与
* for(int i = 0; i < n; i++)
* for(int j = i+1; j < n; j++)
* {}
* 的区别,这个不多说了。
* 7. 我用过的另一个剪枝,但是对 poj 的数据效果一般,
* 用一个数组, Sum[i] 保存 第 i 个木棍之后,即比第 i 枝
* 木棍短或与之相等所有的木棍的长度之和。
* 试想,如果剩余的所有木棍加在一起都不能和我当前的状态拼
* 出一直长为 L 的木棍(从长度来看),还有必要搜下去么?

代码如下:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>

using namespace std;
int sticks[65],n,sum,num,l;//l最小的与木棒的长度 num:圆木棒的个数  sum:圆木棒的总长度
bool mark[65];
bool cmp(int a,int b)
{
return a>b;
}

//s:已经组成的小木棒的个数,le:当前搜索时正在组成的小木条的长度。pos:要组合的小木条的下标位置
bool dfs(int s,int le,int pos)
{
int i;
bool sign = (le == 0?true:false);
if(s==num)return true;
for(i = pos + 1;i < n;i++)
{
if(mark[i])continue;//如果这个小木棒组合过则从下一个开始
if(le + sticks[i]==l)//如果组合刚好组成l长度的木棒那么就要组合下一根木棒了即第s+1根
{
mark[i] = true;
if(dfs(s+1,0,-1))//第s根已经组合成功了组合下一根
return true;
mark[i] = false;
return false;//如果组合失败了那么就要返回false 而且这个木棒的状态应还原为没组合过
}
else if(le + sticks[i]<l)
//如果组合当前这根后长度仍然小于l那么从i开始往下找继续组合第S根木棒
{
mark[i] = true;
if(dfs(s,le+sticks[i],i))//如果可以组成就返回true
return true;
mark[i] = false;
if(sign)return false;
while(sticks[i]==sticks[i+1])i++;
}
}
return false;
}

int main()
{
while(scanf("%d",&n)!=EOF,n)
{
sum = 0;

for(int i = 0; i < n; i++)
{
scanf("%d",&sticks[i]);
sum += sticks[i];
}
sort(sticks,sticks+n,cmp);//将木棒从大到小排序
for(l = sticks[0]; l <= sum; l++)//从最大的开始搜索
{
if(sum%l==0)//如果该长度可以被整除那么可能是结果,否则继续
{
num = sum/l;//num:记录该情况下可以组成的木棒的数目。
memset(mark,false,sizeof(mark));//每种情况都要进行初始化,把所有的木棒的使用状态设为false
if(dfs(1,0,-1))//当返回true时说明搜索成功了,可以组成该长度木条
{
printf("%d\n",l);
break;
}
}
}
}
return 0;
}


思路3:

1:初始状态:有N节木棒
最终状态:这N节木棒恰好被拼接成若干根等长的木棒
从初始状态到最终状态最多有多少条不同的“路径”?
Sum /maxParts。其中Sum是全部N节木棒的长度之和,maxParts是最长一节木棒的长度
每条“路径”对应一个木棒的长度。从木棒长度最小的那条可能“路径”开始,如果成功地的找到了这条“路径”,就解决了问题

2:构造一条木棒长度为L的“路径”:拼接木棒
在未被拼接的木棒中,找出一节最长的,开始拼接
从未拼接的木棒中,选择一个长度合适的木棒,使得拼接后的木棒长度≤L
找到了
在前面的拼接过程中曾试图用过相同长度的一节其他木棒,但发现这样拼接不成功,继续寻找能够进行拼接的木棒
把找到的那节木棒拼接上去。继续进行拼接
继续拼接成功,找到了“路径”
继续拼接不成功,把刚拼接的那节木棒拿下来,继续找下一个合适的未拼接木帮
没有找到:拼接失败

3:在已拼接部分未用长度为L1的木棒,则表明用长度为L1的木棒来拼接时是不成功的
下次拼接时也不能选择长度为L1的木棒,而应该选择长度为L2或L3的木棒
如果长度为L2或L3的木棒也不能选择,则需要替换已拼接部分的最后一节木棒

代码如下:

1 #include <iostream.h>
2 #include <memory.h>
3 #include <stdlib.h>
4 int T, S;
5 int L;
6 int anLength[65];
7 int anUsed[65];
8 int i,j,k;
9 int Dfs(int nUnusedSticks, int nLeft);
10 int MyCompare( const void * e1, const void * e2) {
11     int * p1, * p2;
12     p1 = (int * ) e1;
13     p2 = (int * ) e2;
14     return * p2 - * p1;
15 }
16 main()
17 {
18     while(1) {
19         cin >> S;
20         if( S == 0 )
21             break;
22         int nTotalLen = 0;
23         for( int i = 0; i < S; i ++ ) {
24             cin >> anLength[i];
25             nTotalLen += anLength[i];
26         }
27         qsort(anLength,S,sizeof(int),MyCompare);
28 for( L = anLength[0]; L <= nTotalLen / 2; L ++ ) {
29         if( nTotalLen % L)
30             continue;
31         memset( anUsed, 0,sizeof(anUsed));
32         if( Dfs( S,L)) {
33             cout << L << endl;
34             break;
35         }
36      }
37     if( L > nTotalLen / 2 )
38         cout << nTotalLen << endl;
39     } // while
40 }
41 int Dfs( int nUnusedSticks, int nLeft)
42 // nLeft表示当前正在拼的棍子和 L 比还缺的长度
43 {
44     if( nUnusedSticks == 0 && nLeft == 0 )
45         return true;
46     if( nLeft == 0 ) //一根刚刚拼完
47         nLeft = L;  //开始拼新的一根
48     for( int i = 0;i < S;i ++) {
49         if( !anUsed[i] && anLength[i] <= nLeft) {
50             if( i > 0 ) {
51                 if( anUsed[i-1] == false
52                    && anLength[i] == anLength[i-1])
53                     continue; //剪枝3
54             }
55             anUsed[i] = 1;
56 if ( Dfs( nUnusedSticks - 1,
57                  nLeft - anLength[i]))
58                 return true;
59             else {
60                 anUsed[i] = 0;//说明不能用i作为
61                         //第1条,
62                         //那么要拆以前的
63                         //棍子,i还可能用
64                         //在以前的棍子里,
65                          //因此要 anUsed[i] = 0;
66                 if( anLength[i] == nLeft || nLeft == L)
67                     return false;//剪枝2、1
68             }
69         }
70     }
71     return false;
72 }


思路4:

#include <stdio.h>
#include <stdlib.h>
int sticks[100],n;
bool used[100];
int cmp(const void *x,const void *y)
{
return *(int *)y - *(int *)x;
}
bool find(int left,int num,int len)
{
int i;
if(left == 0 && num == 0)
return 1;
if(left == 0)
left = len;
for(i = 0;i < n;i ++)
{
if(sticks[i] <= left && !used[i])
{
used[i] = 1;
if(find(left - sticks[i],num-1,len))
return 1;
used[i] = 0;
if(sticks[i] == left || left == len)
return 0;
}
}
return 0;
}

int main()
{
int i,sum = 0;
while(scanf("%d",&n) != EOF && n)
{
sum = 0;
for(i = 0;i < n;i ++)
{
scanf("%d",&sticks[i]);
sum += sticks[i];
used[i] = 0;
}
qsort(sticks,n,sizeof(int),cmp);
for(i = sticks[0];i <= sum;i ++)
{
if((sum % i == 0) && find(i,n,i))
{
printf("%d\n",i);
break;
}
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: