Enum:Smallest Difference(POJ 2718)

                  

                  最小的差别

  题目大意:输入一串数字,问你数字的组合方式,你可以随意用这些数字组合(无重复)来组合成一些整数(第一位不能为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

原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/4853362.html