#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int tmpArray[300];
int sticks[64];
char stickUsed[64];
int maxLevel;
int comp( const void *a, const void * b)
{
return *(int *)a-*(int *)b;
}
int comp1( const void *a, const void * b)
{
return *(int *)b-*(int *)a;
}
void merge(int * array, int A1begin, int A1end, int B1begin, int B1end) {
// printf("%d %d %d %d\n", A1begin, A1end, B1begin, B1end);
int totalLength = A1end - A1begin + 1 + B1end - B1begin + 1;
int insertPos = 0;
int A1unInsertedPos = A1begin;
int B1unInsertedPos = B1begin;
while(1) {
if (A1unInsertedPos > A1end || B1unInsertedPos > B1end) {
break;
}
tmpArray[insertPos++] = array[A1unInsertedPos] < array[B1unInsertedPos] ?
array[A1unInsertedPos++] : array[B1unInsertedPos++];
}
int leftDataPosBegin = A1unInsertedPos > A1end ? B1unInsertedPos : A1unInsertedPos;
int leftDataPosEnd = A1unInsertedPos > A1end ? B1end : A1end;
memcpy(tmpArray + insertPos, array + leftDataPosBegin, (leftDataPosEnd - leftDataPosBegin + 1)*sizeof(int));
memcpy(array + A1begin, tmpArray, totalLength*sizeof(int));
}
void mergeSort(int * array, int begin, int end) {
// printf("%d %d\n", begin, end);
if (end <= begin) {
return;
} else {
int middle = (begin + end)/2;
mergeSort(array, begin, middle);
mergeSort(array, middle+1, end);
merge(array, begin, middle, middle+1, end);
}
}
char dfs(int originallength, int length, int begin, int end) {
if (length == 0) {
int unused = -999;
for (int i = 0; i <= end; i++) {
if (!stickUsed[i]) {
unused = i;
break;
}
}
if (unused == -999) {
return 1;
} else {
stickUsed[unused] = 1;
if (dfs(originallength, originallength - sticks[unused], unused + 1, end)) {
return 1;
} else {
stickUsed[unused] = 0;
return 0;
}
}
} else {
// printf("L %d\n", length);
if (begin > end) {
return 0;
}
if (length < 0) {
// printf("return 0\n");
return 0;
}
for (int i = begin; i <= end; i++) {
if (!stickUsed[i]) {
if ( i>0 && !stickUsed[i-1] && (sticks[i] == sticks[i-1])) {
continue;
}
int requiredLength = length - sticks[i];
// printf("choose %d %d\n", sticks[i], requiredLength);
if (requiredLength < 0) {
stickUsed[i] = 0;
continue;
}
stickUsed[i] = 1;
char resulst = dfs(originallength, requiredLength, i + 1, end);
if (resulst) {
// printf("return 1\n");
return 1;
} else {
stickUsed[i] = 0;
}
}
}
// printf("return %d\n", res);
return 0;
}
}
void getShortest(int num, int sum) {
// printf("%d %d\n", max, sum);
// mergeSort(sticks, 0, num-1);
qsort(sticks ,num, sizeof(int), comp1);
// for (int i = 0; i <= num-1; i++) {
// printf("%d ", sticks[i]);
// }
// printf("\n");
int max = sticks[0];
for (int i = max; i <= sum/2; i++) {
// printf("%d\n", i);
if (sum % i == 0) { // a possible length,
maxLevel = sum/i;
if (dfs(i, i, 0, num-1)) {
printf("%d\n", i);
return;
}
}
}
printf("%d\n", sum);
}
void test() {
// int array[] = {46, 78, 1, -99, 27, 356, 88, 1 ,7, 1, 89, 22, 10, 12, 7};
int array[] = {46, 78, 1, -99, 27, 356, 88, 3 ,7, 9, 89, 22, 10, 12, 6};
mergeSort(array, 0, sizeof(array)/sizeof(int) - 1);
for (int i = 0; i < sizeof(array)/sizeof(int); i++) {
printf("%d ", array[i]);
}
printf("\n");
}
int main() {
// test();
int num = 0;
while(cin>>num) {
memset(sticks, 0, sizeof(sticks));
memset(stickUsed,0, sizeof(stickUsed));
// printf("%d", num);
if (!num) {
return 0;
}
int sum = 0;
for (int i = 0; i < num; i++) {
scanf("%d", sticks + i);
sum += sticks[i];
}
getShortest(num, sum);
}
}
208K 16MS G++
深度树状搜索(也叫解答树), 丢了好久了, 一开始还和图的深度遍历搞混了,唉,要好好补补这一块的知识了,树状搜索用的地方其实挺多的.
在摸索这道题的过程中,慢慢的回想起来了深度树搜索的知识和步骤,当时做过一些最基本的题,比如从N个字符中列出所有不同的长度为M的字符组合之类的,
不过这一道题我按照最朴素的深度树搜索来,直接TLE了... :(
不过还是要列一下朴素深度树搜索的步骤,就当深化记忆:
首先,在搜索树的顶点,要确定一些状态值,来标示此状态的特殊性,题目中提供的是一个数组,本质上就是从数组中不断的选择数,看是否能够找到N个数,使其和
正好等于某个数L, 那么某一个状态下,便最起码有两个特殊的状态值:
1.在此状态下, 要找到的某几个数的和 S
2.当前数组被使用的情况(有些数已经在解答树的上几层被使用了,以后就不够再使用)。
对于第一个状态值,一个int变量即可,对于第二的状态值,一般有两种方式:
1.在每一次做出选择的时候,将被选择的数与 当前数组 的第一个元素(原数组的第i个)互换位置,下一层要处理的数组是从原数组的
第i+1开始,这样就保证了,下一层解答使用的数组里面所有的元素都是之前没有被使用过的,而在该层的操作完成以后,要记住将此数组还原,
有点类似于函数调用完恢复上一层调用环境(本质上确实是一样的)。
那么,假设在某一层的可用数组有j个, 而要求的数的和是S ,那么就要遍历这j个数,对每个数k进行一次上面的替换,然后用剩下的j-1的数组,数和为S-A[k]
进行一次新的dfs, 如果某个数的dfs是成功的,那么就恢复环境返回成功,如果某个数dfs不成功,恢复环境,对数组的下一个数dfs,如果遍历完了数组没有
成功的,那么就返回失败。
2. 如果数组长度不是很长,那么可以用一个bitmap保存数组每个元素的使用情况,不用互换数组,每次找第一个没有被使用过的数.
针对这道题,有些特殊的设定:
因为是要求所有的stick都能组合为长度为L的stick,不能有一个stick最后被拉下,这就要求在某一轮dfs拼成功一个L以后,看看是否还有拉下的stick,
如果有,重置S为L,进行新一轮dfs,并返回此dfs的结果作为最终结果.
以上就是朴素解答树。
但是因为TLE的原因,不得不找更好的办法。
后来发现一种解法:
使用bitmap数组来记录数组的使用情况(最多64,可以接受),但是在搜索没有被使用的数的时候,不是从头开始,而是从上一轮选取的元素的位置开始,
直到找到能拼成功L的情况,在找到以后,才从数组头开始遍历,找到第一个没有被使用的数开始新一轮的一样的dfs,最后在拼成功并且没有
剩余可用数组元素的情况下返回成功, 这种思路可以极大的减少运算量(每次不是从头开始搜索遍历,而是从上一轮位置+1开始搜索遍历),并且
也能保证正确性,值得牢记。
还有些提速技巧
1 事先对数组从小到大排序,这样在后面遍历检测数组元素时,如果A[i]已经比L大,那么i到最后的数组元素必然不满足拼成的条件(都>L),返回失败.
2 也是排序,在遍历数组元素时,如果发现某个元素值与前一个元素相等,而前一个元素也是没有被占用状态,这就意味着前一个元素的dfs失败的,那么因为该元素
与其相等,也必dfs失败,可以直接略过.
最后要说一下L是怎么得到的,因为对dfs解答树来说,必须有一个定长L才可以展开,这里其实就是从小到大枚举了,
题目要求求出最小的能拼完的L, 那么首先, L必定>=sticks中的最长的一个(max), 并且sticks的长度sum也必能整除L, 而且L的最大值就是sum
这样,就可以搞一个从max到sum/2(超过sum/2 就必是sum了)的遍历 i,如果 sum能整除i, 那么就可能是满足条件的,以i作为L进行一轮dfs, 如果成功
输出i,否则继续,因为是从小到大,就保证了,最终解必是最小的。
很多题其实都是这种枚举然后dfs的步骤, 枚举难在找到合适的上下限,dfs难在实现和效率.