开始超时,上网搜了下,发现要剪枝才行……附上搜到的剪枝方法:
剪枝1:
所有木棍的长度必须能被4整除
剪枝2:
最长的木棍不能长于正方形的边长
这两个是最容易想到的,用上这两个可以79ms通过
剪枝3:
同样长度的木棍的同样数量的组合只搜索一次。
这个剪枝需要将木棍从大到小排列,在搜索的时候加一句代码就行了,代码很巧妙。
由于数据问题,这个剪枝貌似不管用。
剪枝4:
每条边的木棍按照从大到小的顺序拼接
如果某条木棍不能够作为某边的第一条木棍,那比它短的也不行
想一想还是可以理解,后面的边始终会用到这条长的棍子,那时候可供选择的木棍更少
所以在前面的边拼不成,在后面的边更加拼不成
这个剪枝非常牛逼,不知道谁想出来的,代码只需要一句。太牛逼了。
由于数据的N比较小,这个剪枝相当管用。
无法实现的理想化剪枝:
如果在枚举中,发现用一条长的棍子枚举失败,那么几条短的棍子组成同样长度的棍子也必然不用试验。
这个很理想,但是实现的代价很大。
题目描述:
1011的弱版……给一堆木棒,每根木棒有个长度,求是否能使这些木棒分成4堆,每堆总长度相等
解题报告:
显然可以求得每堆的总长度len=all/4;
然后开始填充len……方法和1011一样……用了几个减枝
1。如果4不能整除all,不用搜索,不可能
2。如果最大木棒的长度大于len,不用搜索,不可能
3。对所有木帮排序,填充时先用长度长的,因为长度长的木帮显然不如短的用得灵活
4。如果已经找到答案,尽快返回上一层
5。判断当前长度是否大于每段的长度,小于就不用搜索了,显然不可能
6。相同长度的木棒不用搜索多次
7。从最接近并小于这根木棒的木棒开始搜索,这根之前的木棒显然都用过了
8。判断当前剩下的段数是否小于4-d,不用搜索,不可能
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int len[21];
int visited[21];
int N, M;
//start:开始的木棍标号 l:已组合的长度, num:已组合成功的目标正方形的边数, length:目标正方形的边长
int DFS(int start, int l, int num, int length)
{
if(num==4) return 1;
int i;
for(i=start; i<M; i++)
{
if( visited[i]==0 )
{
if( l+len[i]==length )
{
visited[i]=1;
if( DFS(0, 0, num+1, length )) return 1;
visited[i]=0;
}
else if( l+len[i]<length)
{
visited[i]=1;
if( DFS(i+1, l+len[i], num, length )) return 1;
visited[i]=0;
}
while( len[i]==len[i+1])//某长度的木棍不能完成某个子问题,那么和它一样长的也不能完成
i++;
}
}
return 0;
}
bool cmp(int a, int b)
{
return a>b;
}
int main()
{
int i, sum, length;
cin>>N;
while( N-- )
{
sum=0;//sum记录所有木棍的长度和
cin>>M;
memset(visited, 0, sizeof(visited));
for(i=0; i<M; i++)
{
cin>>len[i];
sum+=len[i];
}
sort(len, len+M, cmp);
length=sum/4;
if( sum%4==0 && len[0]<=length)//木棍总和能被4整除且最长的木棍小于正方形边长
{
if( DFS(0, 0,0,length))
cout<<"yes"<<endl;
else
cout<<"no"<<endl;
}
else //如果不可以,肯定不能组成正方形
cout<<"no"<<endl;
}
}