很坑爹啊,浪费了一个晚上,其实我做过poj1011的。。那时候都顺利过了。。这个简化版居然纠结那么久。。最后发现没记录当前所到木棍,一开始觉得这个小小的剪枝就没必要啦,在poj1011时我就没做这个剪枝,都轻松过了。。。TLE了无数次,才决定试着改下。。⊙﹏⊙b汗。。0ms过了。。看来真的任何一点小小的代码都不容忽视的。。好吧,继续加油~
/*
zoj_1909 搜索
很经典的一道题。poj1011的简化版。。
剪枝:
1.从长到短排序,这样搜索失败时在离根部不远就结束的可能性大。
2.长度总和%4==0
3.最短木棍大于边长度
4.最后一条边不用搜
5.对相同长度木棍分类,避免搜索的时候做重复的搜索。
6.当前最长木棍必须用来组边。否则可直接退出。因为如果当前都不能用
后面也永远无法再用到。这个剪枝威力巨大。
7.记录当前所到木棍的序号,即为代码中的index。这个在此题中关键,不
记录必然TLE(我一开始就没记,每次从0开始,TLE无数次)
*/
#include <iostream>
#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
struct stick
{
int len;
int num;
}s[25];
int m,ave;
bool cmp( stick a,stick b )
{
return a.len>b.len;
}
bool dfs( int rest,int sum,int index )
{
int i;
if( sum==ave ) return true; //剪枝4
for( i=index;i<m;i++ ) //剪枝7
{
if( s[i].num!=0 && s[i].len<=rest )
{
s[i].num--;
if( rest==s[i].len )
{
if( dfs( ave,sum-rest,0 ) )
return true;
}
else if( dfs( rest-s[i].len,sum-s[i].len,i ) )
return true;
s[i].num++;
if( rest==ave ) return false; //剪枝6
}
}
return false;
}
int main()
{
int n,i,j,k,t,maxi,sum;
scanf( "%d",&n );
while( n-- )
{
scanf( "%d",&m );
sum=0; k=0; maxi=-1;
for( i=0;i<m;i++ )
{
scanf( "%d",&t );
sum+=t ;
for( j=0;j<k;j++ )
{
if( s[j].len==t ) //剪枝5
{
s[j].num++;
break;
}
}
if( j==k )
{
s[k].len=t;
s[k].num=1;
k++;
if( maxi<t ) maxi=t;
}
}
m=k;
ave=sum/4;
if( sum%4!=0 || maxi>ave ) //剪枝2.剪枝3
printf( "no\n" );
else
{
sort( s,s+m,cmp ); //剪枝1
if( dfs( ave,sum,0 ) ) printf( "yes\n" );
else printf( "no\n" );
}
}
return 0;
}