丑陋的数字UVA

时间:2016-07-17 11:11:00

标签: c++ arrays algorithm

我正在尝试从UVA问题集中计算第1500个丑陋的数字。 136。

(参考:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&page=show_problem&problem=72

我的算法很简单:

  • 跟踪数组 un 中的所有丑陋数字。
  • un [i] 成为i + 1丑陋的数字。

步骤:

  1. 使用tmp变量 cur 来保存 ith 丑陋数字的索引。

  2. 计算 un [cur] x 2 un [cur] x 3 un [cur] x 5 。< / p>

  3. 使用集合消除重复项并将其存储到 un

  4. 对数组进行排序,以确保 un [i + 1] 始终是最小的。

  5. 增加cur变量,使其成为 i + 1th 丑陋数字的索引。

  6. 重复直到阵列中生成了1500个丑陋的数字。

  7. 我的代码:

    # include<iostream>
    # include<set>
    # include<algorithm>
    
    using namespace std;
    
    int main(void) {
        long long un[1500] = { 0 };
        set<long long> us;
        un[0] = 1;
        us.insert(1);
        int i = 1,unE = 0,cur = 0;
    
        while(true) {
            unE = 0;
            sort(un,un+i);
            if(us.find(un[cur]*2) == us.end()) {
                un[i+unE] = un[cur]*2;
                us.insert(un[i+unE]);
                unE++;
            }
            if(i + unE > 1500 - 1) {
                break;
            }
            if(us.find(un[cur]*3) == us.end()) {
                un[i+unE] = un[cur]*3;
                us.insert(un[i+unE]);
                unE++;
            }
            if(i + unE > 1500 - 1) {
                break;
            }
            if(us.find(un[cur]*5) == us.end()) {
                un[i+unE] = un[cur]*5;
                us.insert(un[i+unE]);
                unE++;
            }
            i+=unE;
            cur++;
        }
        sort(un,un+1500);
    
        for(int i = 0; i < 1500; i++) {
            cout << un[i] << " ";
        }
        cout << un[1500-1] << endl;
    }
    

    我的算法没有输出正确的数字,即859963392.我得到一个更大的数字。有人可以指出我正确的方向吗?

2 个答案:

答案 0 :(得分:2)

更简单的代码但更难以阅读解决方案is

 #include <iostream>

using namespace std;

int main(){
    const int n = 1499;
    int a [ 1500 ];
    int p1(0), p2(0), p3(0), end(0);
    a [ 0 ] = 1;
    while ( end < n ){
        while ( a [ p1 ] * 2 <= a [ end ] )  ++ p1;
        while ( a [ p2 ] * 3 <= a [ end ] )  ++ p2;
        while ( a [ p3 ] * 5 <= a [ end ] )  ++ p3;
        if ( a [ p1 ] * 2 < a [ p2 ] * 3 && a [ p1 ] * 2 < a [ p3 ] * 5 )
            a [ ++ end ] = a [ p1 ++ ] * 2;
        else    if ( a [ p2 ] * 3 < a [ p3 ] * 5 )
                    a [ ++ end ] = a [ p2 ++ ] * 3;
                else    a [ ++ end ] = a [ p3 ++ ] * 5;
    }
    cout << "The 1500'th ugly number is " << a [ end ] << ".\n";
    return 0;
}

对于记录
,Bruteforce解决方案,只需检查所有数字是否丑陋,并保持丑陋计数在我的计算机上花费超过20秒,并使用以下代码:

//uva136 preparer  
#include <iostream>

using namespace std;

typedef  long long ll;



bool is_ugly(int in)
{

    while( true)
    {
        if(in % 5 ==0)
            in /= 5;
        else if( in %2==0)
            in /=2 ;
        else if (in % 3 == 0 )
            in/=3;
        else
            break;

    }
    if(in==1)
        return true ;
    else 
        return false ;

}

int main()
{
    int c=0 ; 
    ll j=6;
    int i=6;
    for(j =6;(i<1501) ; j++)
    {
        if(isugly(j))
            i++;
    }
    cout<<j-1<<endl<<double(clock())/CLOCKS_PER_SEC;// because at the last iteration of four , j is updated to j+1 and we should minus it by one to make it no count .
    return 0;
}

答案 1 :(得分:2)

您的算法几乎是正确的,但错误是您不应该在生成1500个数字时停止,而是在“curr”达到第1500个数字时停止。这是因为并非'curr'之后的所有丑陋数字都已生成,你只能确定在'curr'之前你有任何丑陋的数字。优化算法的另一个建议是在'curr'之后为所有数字使用堆,这样你不需要每次都对整个数组进行排序,而且根本不需要使用集合。这是我的代码:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
vector<long long> un; //using vector because we don't know how many ugly numbers we will need to generate
//If we decide to use an array (the way you did) it is better to define it outside of main().
priority_queue<long long> nun; //priority queue used for storing the next ugly numbers
const int TARGET=1500; //always good to store magical numbers as constants instead of having them appear all over the code
int main()
{
    un.push_back(1); //adding the first ugly number
    for (int i=0;i<TARGET-1;++i)
    /*
    We have already found the first ugly number (1),
    so we only need to find TARGET-1 more.
    */
    {
        nun.push(-un[i]*2);
        nun.push(-un[i]*3);
        nun.push(-un[i]*5);
        //adding the next ugly numbers to the heap
        /*
        Adding them as negative numbers because priority_queue
        keeps the largest number on the top and we need the smallest.
        */
        while (-nun.top()==un[i])
        {
            nun.pop();
            //removing duplicates
            /*
            We can prove that we will never have more than 3 copies
            of a number in the heap and thus that this will not
            affect the performance.
            1) We will never have more than one copy of a number in un.
            2) Each number can be added to nun in 3 different ways:
            by multiplying a number form un by 2, 3 or 5.
            */
        }
        un.push_back(-nun.top());
        nun.pop();
        //adding the next ugly number to un
    }
    cout<<un[TARGET-1]<<endl;
    /*
    Indexing starts at 0 so the TARGETth number is at index TARGET-1.
    */
    return 0;
}

我的程序确实输出了859963392,这是正确答案。

编辑:在考虑了一下之后,我把它归结为线性复杂性。这是代码:

#include<iostream>
#include<vector>
//#include<conio.h>
using namespace std;
vector<long long> un; //using vector because we don't know how many ugly numbers we will need to generate
//If we decide to use an array (the way you did) it is better to define it outside of main().
const int TARGET=1500; //always good to store magical numbers as constants instead of having them appear all over the code
int l2=0,l3=0,l5=0; //store the indexes of the last numbers multiplied by 2, 3 and 5 respectively
int main()
{
    un.push_back(1); //adding the first ugly number
    for (int i=0;i<TARGET-1;++i)
    /*
    We have already found the first ugly number (1),
    so we only need to find TARGET-1 more.
    */
    {
        un.push_back(min(min(un[l2]*2,un[l3]*3),un[l5]*5));
        //adding the next ugly number to un
        if (un[i+1]==un[l2]*2) //checks if 2 multiplied by the number at index l2 has been included in un, if so, increment l2
        {
            ++l2;
        }
        if (un[i+1]==un[l3]*3) //checks if 3 multiplied by the number at index l3 has been included in un, if so, increment l3
        {
            ++l3;
        }
        if (un[i+1]==un[l5]*5) //checks if 5 multiplied by the number at index l5 has been included in un, if so, increment l5
        {
            ++l5;
        }
        /*
        Basically only one of the variables l2, l3 and l5 (the one we used) will be incremented in a cycle unless we can get a number
        in more than one way, in which case incrementing more than one of them is how we avoid duplicates.
        Uncomment the commented code to observe this.
        P.S. @PaulMcKenzie I can deal without a debugger just fine.
        */
        //cerr<<i<<": "<<l2<<"("<<un[l2]*2<<") "<<l3<<"("<<un[l3]*3<<") "<<l5<<"("<<un[l5]*5<<") "<<un[i+1]<<endl;
        //getch();
    }
    cout<<un[TARGET-1]<<endl;
    /*
    Indexing starts at 0 so the TARGETth number is at index TARGET-1.
    */
    return 0;
}

EDIT2:第一个解决方案根本不需要矢量,因为它不使用以前的数字。因此,您可以使用单个变量以记忆方式对其进行优化。这是一个实现:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
long long un; //last ugly number found
priority_queue<long long> nun; //priority queue used for storing the next ugly numbers
const int TARGET=1500; //always good to store magical numbers as constants instead of having them appear all over the code
int main()
{
    un=1; //adding the first ugly number
    for (int i=0;i<TARGET-1;++i)
    /*
    We have already found the first ugly number (1),
    so we only need to find TARGET-1 more.
    */
    {
        nun.push(-un*2);
        nun.push(-un*3);
        nun.push(-un*5);
        //adding the next ugly numbers to the heap
        /*
        Adding them as negative numbers because priority_queue
        keeps the largest number on the top and we need the smallest.
        */
        while (-nun.top()==un)
        {
            nun.pop();
            //removing duplicates
            /*
            We can prove that we will never have more than 3 copies
            of a number in the heap and thus that this will not
            affect the performance.
            1) We will never have more than one copy of a number in un.
            2) Each number can be added to nun in 3 different ways:
            by multiplying a number form un by 2, 3 or 5.
            */
        }
        un=-nun.top();
        nun.pop();
        //adding the next ugly number to un
    }
    cout<<un<<endl;
    /*
    Indexing starts at 0 so the TARGETth number is at index TARGET-1.
    */
    return 0;
}

总之,我们有两个相互竞争的解决方案(假设我们不需要存储数字,那么与第一个相比,最后一个解决方案在各个方面都更好(或者更差)。其中一个具有线性时间复杂度,以及线性存储器复杂度(但它不是N,因为更优化的解决方案将释放用于我们不再需要的数字的存储器。但是作为N随着N接近无穷大,内存复杂度接近N.)而另一个具有NlogN时间复杂度和我们需要确定的一些内存复杂性,因此l5越来越大l5落后于l2和l3。每次我们向堆中添加3个数字然后取出1到3之间的任何数字(因为一个数字最多可以在堆中3次,我们已经取出其中一个副本,我们可以删除其他2个重复数) 。因此,它的存储器复杂度可以是常数和2N之间的任何值。我会在进行进一步分析后编辑这个答案,但我的第一印象是,对于大Ns,内存复杂度将小于N.

在更进一步的结论中,线性解决方案速度更快,NlogN解决方案可能在记忆方面更好,但尚未得到证实。

EDIT3:NlogN解决方案肯定更差。随着N越来越大,越来越多的数字可以被2,3和5整除,直到几乎所有数字都可以被所有三个整除。因为在几乎每个循环中,我们从堆中删除3个数字,因此它不会增长。因此当N接近无穷大时,存储器复杂度接近常数。您可以通过在每个周期或最后输出优先级队列的大小来测试这一点。例如,当N = 5000时,堆的大小是1477.但是当N达到10000时,大小只有2364。

似乎这个解决方案会更好,但实际上我在原始分析中犯了一个错误。 l5并没有越来越落后。正如我对大N所解释的那样,所有数字都可以被所有三个2,3和5整除。因为l5几乎每次都会增加,而我们需要存储的数字的数量几乎保持不变。例如,当N = 5000时,l5 = 4308,并且由于我们只需要存储l5和N之间的数字,我们需要存储690个数字。当N达到10000时,l5 = 8891,我们需要存储1107个数字。它几乎是双倍的,所以它接近常数并不明显,但如果我们进行更多的测试,那么显然它正在减速。

从理论上讲,内存复杂性应该以大约相同的速率减慢,但堆大小的初始增加会使它占用更多内存。

总之(第三次是魅力)线性解决方案在速度和记忆方面都更好(如果动态内存分配在我的解决方案中实现则不是,因为对于如此小的N-1500不需要)。