题目大意:
给定一组不同长度的棍子,是否可以将它们首尾相连形成一个正方形?
输入
输入的第一行包含 N,即测试用例的数量。每个测试用例都以整数 4 <= M <= 20 开始,即棒的数量。接下来是 M 个整数;每个都给出一根棍子的长度一个介于 1 和 10,000 之间的整数。
输出
对于每种情况,如果可以形成正方形,则输出包含“yes”的行;否则输出“no”。
样本输入
3
4 1 1 1 1
5 10 20 30 40 50
8 1 7 2 6 4 4 3 5
样本输出
yes
no
yes
【分析】
题面要求判断能否搜索出一个方案让这些木棍形成正方形。首先,将木棍长度进行累加,得到总长length;然后将length除以4得到正方形的边长side;之后,将木棍拼凑成长度为side 的边,拼凑完一条边后,再拼凑下一条,直到拼凑出4条长度为side 的边,从而构成一个正方形。
搜索状态三元组(sum, number ,position),其中 sum是当前拼凑的木棍长度,number是已拼凑成边长的数量, position是当前木棍的编号。需要搜索到的目标状态为number=3。为什么不是4呢﹖因为如果总长length能够整除4,且已经拼凑出3条边,那么剩下的木棍必定可以构成最后的一条边。由于从第一根木棍开始拼凑,故搜索初始状态为(0,0,0)。
在搜索过程中,可以通过放弃对某些不可能产生结果的子集的搜索,达到提高效率的目的。这样的技术称为剪枝。本题中可以剪枝的途径如下:
①如果总长length不能整除4,那么必定无法构成正方形。
②如果某根木棍的长度大于边长side,那么必定无法构成正方形。
③拼凑中发现某长度的木棍无法构成当前边时,再遇等长木棍时可跳过(需排序)。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int Max = 25;
bool visit[Max];
int a[Max];
int side, m;
bool DFS(int sum, int num, int position){
if(num == 3)
return true;
int sample = 0; //剪枝3:记录上一次失败的长度
for(int i = position; i < m; i++){
if(a[i] > side || visit[i] == true || sum + a[i] > side || a[i] == sample)
continue;
visit[i] = true;
//cout << "sum + a["<<i<<"]="<<sum + a[i]<<" side=" << side << " num="<<num<<endl;
if(sum + a[i] == side){
if(DFS(0, num + 1, 0))
return true;
else
sample = a[i];
}else{
if(DFS(sum + a[i], num, i + 1))
return true;
else
sample = a[i];
}
visit[i] = false;
}
return false;
}
bool cmp(int x, int y){
return x > y;
}
int main(){
int n;
cin >> n;
while(n--){
int length = 0;
cin >> m;
for(int i = 0; i < m; i++){
cin >> a[i];
length += a[i];
}
memset(visit, false, sizeof(visit));
if(length % 4 != 0) //剪枝1
cout << "no" <<endl;
else{
side = length / 4;
sort(a, a + m, cmp);
if(a[0] > side) //剪枝2
cout << "no" << endl;
visit[0] = true;
if(DFS(0, 0, 0))
cout << "yes" << endl;
else
cout << "no" << endl;
}
}
system("pause");
return 0;
}