题目大意
给你M根长度可能不同的棍子,问你用着M根棍子能否拼成一个正方形.
思路分析
首先将M根棍子的总长sum求出,sum%4必须==0且任意一个棍子的长度<=sum/4.
用vis[i]数组表示当前第i根棍子是否被使用了.
然后用dfs来判断能否构成正方形的四边即可.其中
bool dfs(int num, int len, int j)
表示当前正在构造第num
根棍子且当前棍子已经凑到了len
的长度,下一次从第j
个木棍开始搜索。这里注意网上很多解法都是对棍子排序了的,其实不用排序也行.因为我们用到的是定序剪枝,只要棍子的顺序固定就行.
源代码中最重要的剪枝是dfs函数中的下一次开始begin位置的记录,假设我们当前这步与下一步都是在构造同一条边,那么我们下次搜索所有棍子只需要从begin+1位置开始找即可,不用从0开始选。为什么?
假设我们当前刚设置完vis[5]=1
,表示第5
根棍子我们在还是left
长度的时候用了,现在我们还是left-len[5]
长度的时候要不要去用len[2]
来尝试配对一下?不用,肯定无解.因为搜索的时候我们是先找的len[2]
的,如果len[2]+len[5]+….
有一个可行解(可以配对成当前cnt
边)的话,我们之前肯定已经找到了且推出了dfs这个函数.但是现在明显我们还没找到,所以len[2]+len[5]+…
不可能是一个可行解,所以直接从5后面的位置继续找即可.如果还不能理解,就把本题想象成我们只需要配对一条正方形的边,不用配4边了.
//164K 79MS
#define inf 0x3f3f3f3f
#define vec vector<int>
#define P pair<int,int>
#define ll long long
#define MAX 25
int T, n, a[MAX], vis[MAX], sum, ma;
//目前在拼凑num个木棍,第num个长度为0,上一个用了j号木棍
bool dfs(int num, int len, int j) {
if (num == 5)return true;
for (int i = j + 1; i < n; i++) {
if (vis[i])continue;
if (len + a[i] == sum / 4) {
vis[i] = 1;
if (dfs(num + 1, 0, 0))return true;
vis[i] = 0;
}
else if (len + a[i] < sum / 4) {
vis[i] = 1;
if (dfs(num, len + a[i], i))return true;
vis[i] = 0;
}
}
return false;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
sum = 0; ma = 0;
for (int i = 0; i < n; i++)
scanf("%d", &a[i]), sum += a[i], ma = max(ma, a[i]);
if (ma > sum / 4 || sum % 4 != 0) {
cout << "no" << endl;
continue;
}
memset(vis, 0, sizeof(vis));
if (dfs(1, 0, -1))cout << "yes" << endl;
else cout << "no" << endl;
}
}