题目大意:输入一串数字,问你数字的组合方式,你可以随意用这些数字组合(无重复)来组合成一些整数(第一位不能为0),然后问你组合出来的整数的差最小是多少?
这一题可以用枚举的方法去做,这里我就用枚举和贪心(这个非常爽)的方法去。
首先这一题的限制是1000ms,我们知道全枚举的时间肯定是超时的,所以我们必须裁枝,我们这样看,我们得到的最小值,必须是两个数差别最小的时候才会出现,所以这里我们可以只用枚举lenth/2时候的数串的情况,这样就降低了很多的复杂度。
但是这样还是不够,但是我们知道,如果我们规定枚举的子串A一定比子串B大,则当枚举第一个的时候,如果枚举第二个的数的某个位(比如枚举012467,子串A为204,当子串B的百位枚举到7的时候)我们就可不必往十位和各位枚举了)这样也会缩减很多时间,还要把最高位是0的情况排除,这也是必须做的情况
当然,这一题还有一些很特殊的情况我们要考虑,比如当只输入01的时候,我们可能得不到结果,因为这个时候ans可能还是INF,这些情况要单独考虑
1 #include <iostream> 2 #include <functional> 3 #include <algorithm> 4 5 using namespace std; 6 7 //全部用全局定义,防止递归太深产生MLE 8 static bool used_A[10]; 9 static bool used_B[10]; 10 static int input[10]; 11 static int zero_set[6] = { 1, 10, 100, 1000, 10000, 100000 };//补0最多补5个 12 static int valueA, cutA, cutB, ans, length; 13 14 void DFS_A(const int, const int); 15 void DFS_B(const int, const int); 16 17 int main(void)//双DFS法 18 { 19 int case_sum; 20 char tmp; 21 while (~scanf("%d", &case_sum)) 22 { 23 getchar();//消掉回车 24 for (int i = 0; i < case_sum; i++) 25 { 26 length = 0; 27 while ((tmp = getchar()) != ' ') 28 { 29 if (tmp != ' ') 30 input[length++] = tmp - '0'; 31 } 32 ans = INT_MAX; 33 cutA = length / 2;//length要砍一半 34 cutA = cutA>length - cutA ? cutA : length - cutA; 35 cutB = length - cutA; 36 valueA = 0; 37 38 memset(used_A, 0, sizeof(used_A)); 39 40 DFS_A(0, 0); 41 if (ans != INT_MAX) 42 cout << ans << endl; 43 else//这个情况是针对x 0的情况的 44 cout << valueA << endl; 45 } 46 } 47 return 0; 48 } 49 50 void DFS_A(const int sumA, const int level) 51 { 52 //函数DFS_A,枚举数段A的所有可能,默认A的大小要比B的大 53 if (sumA == 0 && level == 1)//出现第一位是0的情况,不用再枚举下去了 54 return; 55 if (level== cutA) 56 { 57 valueA = sumA; 58 memset(used_B, 0, sizeof(used_B)); 59 DFS_B(0, 0); 60 } 61 else if (level < cutA) 62 { 63 for (int i = 0; i < length; i++) 64 { 65 if (!used_A[i]) 66 { 67 used_A[i] = 1; 68 DFS_A(sumA * 10 + input[i], level + 1); 69 used_A[i] = 0;//回溯 70 } 71 } 72 } 73 } 74 75 void DFS_B(const int sumB, const int level) 76 { 77 //函数DFS_B,枚举数段B的所有可能,并对数值进行裁枝 78 if (sumB == 0 && level == 1)//出现第一位是0的情况,不用再枚举下去了 79 return; 80 int tmp = sumB * zero_set[cutB - level]; 81 if (tmp >= valueA)//默认A的大小要比B的大 82 return; 83 else if (cutB == level) 84 ans = min(ans, valueA - sumB); 85 else if (level < cutB) 86 { 87 for (int i = 0; i < length; i++) 88 { 89 if (!used_A[i] && !used_B[i]) 90 { 91 used_B[i] = 1; 92 DFS_B(sumB * 10 + input[i], level + 1); 93 used_B[i] = 0;//回溯 94 } 95 } 96 } 97 }
但是这一份代码
在评测机跑了875MS,我不开心,怎么会那么慢?
其实一开始参考了这里http://blog.csdn.net/z309241990/article/details/19548297的方法,回过头来再看别人的判断条件
1 void DFS_B(const int sumB, const int level) 2 { 3 //函数DFS_B,枚举数段B的所有可能,并对数值进行裁枝 4 if (sumB == 0 && level == 1)//出现第一位是0的情况,不用再枚举下去了 5 return; 6 int tmp = sumB * zero_set[cutB - level]; 7 if (tmp >= valueA 8 && (ans <= abs(valueA - tmp) && level > 0)) 9 return; 10 else if (cutB == level) 11 ans = min(ans, abs(valueA - sumB)); 12 else if (level < cutB) 13 { 14 for (int i = 0; i < length; i++) 15 { 16 if (!used_A[i] && !used_B[i]) 17 { 18 used_B[i] = 1; 19 DFS_B(sumB * 10 + input[i], level + 1); 20 used_B[i] = 0;//回溯 21 } 22 } 23 } 24 }
很好变成375MS,减了差不多一半
后来想了一下,我这个tmp>=valueA真是画蛇添足,根本就不需要,其实ans<=min(ans,abs(valueA-sumB))这个条件就已经排除了对称的情况了
比如如果一开始已经出了A:176和B:204的差值28,那么下一次A:204和B:167(注意我的顺序),就直接不用枚举176,可能你会问,不是还会有176的时候的更小的值吗?可是这个情况,绝对会被对称的情况优先排除,所以是不用考虑的
最后代码:
1 #include <iostream> 2 #include <functional> 3 #include <algorithm> 4 5 using namespace std; 6 7 //全部用全局定义,防止递归太深产生MLE 8 static bool used_A[10]; 9 static bool used_B[10]; 10 static int input[10]; 11 static int zero_set[6] = { 1, 10, 100, 1000, 10000, 100000 };//补0最多补5个 12 static int valueA, cutA, cutB, ans, length; 13 14 void DFS_A(const int, const int); 15 void DFS_B(const int, const int); 16 17 int main(void)//双DFS法 18 { 19 int case_sum; 20 char tmp; 21 while (~scanf("%d", &case_sum)) 22 { 23 getchar();//消掉回车 24 for (int i = 0; i < case_sum; i++) 25 { 26 length = 0; 27 while ((tmp = getchar()) != ' ') 28 { 29 if (tmp != ' ') 30 input[length++] = tmp - '0'; 31 } 32 ans = INT_MAX; 33 cutA = length / 2;//length要砍一半 34 cutB = length - cutA; 35 memset(used_A, 0, sizeof(used_A)); 36 37 DFS_A(0, 0); 38 if (ans != INT_MAX) 39 cout << ans << endl; 40 else//这个情况是针对x 0的情况的 41 cout << valueA << endl; 42 } 43 } 44 return 0; 45 } 46 47 void DFS_A(const int sumA, const int level) 48 { 49 //函数DFS_A,枚举数段A的所有可能,默认A的大小要比B的大 50 if (sumA == 0 && level == 1)//出现第一位是0的情况,不用再枚举下去了 51 return; 52 if (level== cutA) 53 { 54 valueA = sumA; 55 memset(used_B, 0, sizeof(used_B)); 56 DFS_B(0, 0); 57 } 58 else if (level < cutA) 59 { 60 for (int i = 0; i < length; i++) 61 { 62 if (!used_A[i]) 63 { 64 used_A[i] = 1; 65 DFS_A(sumA * 10 + input[i], level + 1); 66 used_A[i] = 0;//回溯 67 } 68 } 69 } 70 } 71 72 void DFS_B(const int sumB, const int level) 73 { 74 //函数DFS_B,枚举数段B的所有可能,并对数值进行裁枝 75 if (sumB == 0 && level == 1)//出现第一位是0的情况,不用再枚举下去了 76 return; 77 int tmp = sumB * zero_set[cutB - level]; 78 if (ans <= abs(valueA - tmp) && level > 0)//如果补0以后大小都比ans大了,不用再搜了 79 return; 80 else if (cutB == level) 81 ans = min(ans, abs(valueA - sumB)); 82 else if (level < cutB) 83 { 84 for (int i = 0; i < length; i++) 85 { 86 if (!used_A[i] && !used_B[i]) 87 { 88 used_B[i] = 1; 89 DFS_B(sumB * 10 + input[i], level + 1); 90 used_B[i] = 0;//回溯 91 } 92 } 93 } 94 }
这个已经很快了
但是这个还可以用贪心去做,但是做起来异常复杂容易出错,不过我还是去做了这个异常复杂的贪心算法(其实也不算复杂),哈哈哈!
那怎么做呢?
主要思路就是,这个串是顺序的,如果是偶数,那么就找相邻差值最小的两个数(不包括0),假设两个子串的代表整数位n和m(n>m)那么这两个相邻第i+1个整数就作为n的最高位,i个整数作为m的最高位,然后从最小到大(包括0)填充n的剩余位置,从大到小填充m的剩余位置(如果存在两个相邻元素的差值相等,我们要把这些相邻元素都找出来并且做相同的步骤,最后比较得出的最小值)
如果是奇数,那么我们就把第一个不为0的数作为n的最高位,倒数最后一个数作为m的最高位,然后还是从小到大填充n的剩余位置,从大到小填充m的剩余位置(一定要注意千万不要数组越界,我就吃过这样的亏)
1 #include <iostream> 2 #include <functional> 3 #include <algorithm> 4 5 using namespace std; 6 7 static int input[10]; 8 static int length, cutA, cutB; 9 static int zero_set[6] = { 1, 10, 100, 1000, 10000, 100000 }; 10 11 int cal(const int, const int); 12 void cal_odd(const int, const int); 13 14 int fcmop(const void *a, const void *b) 15 { 16 return *(int *)a - *(int *)b; 17 } 18 19 int main(void) 20 { 21 int case_sum, tmp; 22 23 scanf("%d", &case_sum); 24 getchar(); 25 for (int i = 0; i < case_sum; i++) 26 { 27 length = 0; 28 while ((tmp = getchar()) != ' ') 29 { 30 if (tmp != ' ') 31 input[length++] = tmp - '0'; 32 } 33 qsort(input, length, sizeof(int), fcmop); 34 cutA = length / 2; 35 cutA = cutA > length - cutA ? cutA : length - cutA; 36 cutB = length - cutA; 37 if (length == 0) 38 cout << 0 << endl; 39 else if (length == 1) 40 cout << input[0] << endl; 41 else if (length == 2) 42 cout << abs(input[1] - input[0]) << endl; 43 else 44 { 45 int min_delta, min_sum; 46 if (input[0] != 0) 47 { 48 if (cutA == cutB)//如果两个子串的长度相同,则取差值最小的相邻数,大的作为n的最高位,小的作为m的最高位,n的剩下位从小的补齐,m从大的补齐 49 { 50 min_sum = INT_MAX; 51 min_delta = input[1] - input[0]; 52 for (int i = 0; i < length - 1; i++) 53 { 54 if (input[i + 1] - input[i] < min_delta) 55 { 56 min_delta = input[i + 1] - input[i]; 57 min_sum = min(min_sum, cal(i + 1, i)); 58 } 59 else if (input[i + 1] - input[i] == min_delta) 60 min_sum = min(min_sum, cal(i + 1, i)); 61 } 62 cout << min_sum << endl; 63 } 64 else cal_odd(0, length - 1); 65 } 66 else//input[0]==0; 67 { 68 if (cutA == cutB)//如果字串长度相同,A-B则A的最高位要比B的最高位的差值要最小且比他大,且要从1开始 69 { 70 min_sum = INT_MAX; 71 min_delta = input[2] - input[1]; 72 for (int i = 1; i < length - 1; i++) 73 { 74 if (input[i + 1] - input[i] < min_delta) 75 { 76 min_delta = input[i + 1] - input[i]; 77 min_sum = min(min_sum, cal(i + 1, i)); 78 } 79 else if (input[i + 1] - input[i] == min_delta) 80 min_sum = min(min_sum, cal(i + 1, i)); 81 } 82 cout << min_sum << endl; 83 } 84 else cal_odd(1, length - 1);//如果cutA!=cutB,那么直接选一头一尾的最小 85 } 86 } 87 } 88 return 0; 89 } 90 91 int cal(const int posAS, const int posBS) 92 { 93 //专门算偶数重复的情况 94 int sumA = 0, sumB = 0, lenA = 1, lenB = 1; 95 sumA = input[posAS]; sumB = input[posBS]; 96 for (int i = 0; lenA < cutA; i++) 97 { 98 if (i != posAS && i != posBS) 99 { 100 lenA++; 101 sumA = sumA * 10 + input[i]; 102 } 103 } 104 for (int i = length - 1; lenB < cutB; i--) 105 { 106 if (i != posAS && i != posBS) 107 { 108 ++lenB; 109 sumB = sumB * 10 + input[i]; 110 } 111 } 112 return sumA - sumB; 113 } 114 115 void cal_odd(const int posAS, const int posBS) 116 { 117 int lenA = 1, lenB = 1, sumA = 0, sumB = 0; 118 sumA = input[posAS]; sumB = input[posBS]; 119 for (int i = 0; lenA < cutA; i++) 120 { 121 if (i != posAS && i != posBS) 122 { 123 ++lenA; 124 sumA = sumA * 10 + input[i]; 125 } 126 } 127 for (int i = length - 2; lenB < cutB; i--) 128 { 129 if (i != posAS && i != posBS) 130 { 131 ++lenB; 132 sumB = sumB * 10 + input[i]; 133 } 134 } 135 cout << sumA - sumB << endl; 136 }
最后最快的速度,32ms