• 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

  • 相关阅读:
    在一个字符串中找到第一个只出现一次的字符
    查找最小的k个数
    动规:最大上升子序列
    平衡二叉树
    【笔记】php和mysql结合 搞了一个表出来
    设计模式心得(既设计模式篇终章):描述设计模式时的通用公式
    分享系列 之 linux IO原理与几种零拷贝机制的实现
    近期分享:BIO 与 NIO 的实质区别到底是什么?
    源码阅读笔记 之 ThreadLocal —— 不复杂,却有点绕的一个 per thread API
    小脑袋瓜充满了问号:为什么AMQP可以叫做 Advanced?JMS就要low一等吗?
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/4853362.html
Copyright © 2020-2023  润新知