N可分的最小正数

时间:2012-03-24 12:10:47

标签: algorithm math

1·; = N< = 1000

如何找到可被N整除的最小正数,其数字和应该等于N.

例如:
N:结果
1:1个
10:190

算法不应超过2秒。

任何想法(Pseudocode,pascal,c ++或java)?

5 个答案:

答案 0 :(得分:1)

设f(len,sum,mod)为bool,这意味着我们可以构建一个数字(可能带有前导零),长度为len + 1,数字总和等于sum,并在潜水时给出mod。

然后f(len,sum,mod)=或(f(len-1,sum-i,mod- i * 10 ^ len),对于i从0到9)。然后你可以找到最小的l,即f(l,n,n)为真。之后只需找到第一个数字,然后是第二个数字,依此类推。

#define FOR(i, a, b) for(int i = a; i < b; ++i)
#define REP(i, N) FOR(i, 0, N)

#define FILL(a,v) memset(a,v,sizeof(a))



const int maxlen = 120;
const int maxn = 1000;

int st[maxlen];
int n;

bool can[maxlen][maxn+1][maxn+1];
bool was[maxlen][maxn+1][maxn+1];

bool f(int l, int s, int m)
{
    m = m%n;
    if(m<0)
        m += n;

    if(s == 0)
        return (m == 0);
    if(s<0)
        return false;
    if(l<0)
        return false;   

    if(was[l][s][m])
        return can[l][s][m];

    was[l][s][m] = true;
    can[l][s][m] = false;

    REP(i,10)
        if(f(l-1, s-i, m - st[l]*i))
        {
            can[l][s][m] = true;
            return true;
        }
    return false;
}


string build(int l, int s, int m)
{
    if(l<0)
        return "";
    m = m%n;
    if(m<0)
        m += n;
    REP(i,10)
        if(f(l-1, s-i, m - st[l]*i))
        {
            return char('0'+i) + build(l-1, s-i, m - st[l]*i);
        }
    return "";
}

int main(int argc, char** argv)
{
    ios_base::sync_with_stdio(false);

    cin>>n;
    FILL(was, false);   
    st[0] = 1;
    FOR(i, 1, maxlen)
        st[i] = (st[i-1]*10)%n;
    int l = -1;
    REP(i, maxlen)
        if(f(i, n, n))
        {
            cout<<build(i,n,n)<<endl;
            break;
        }

    return 0;
}

注意,它使用~250 MB的内存。

编辑:我找到了这个解决方案运行的测试,有点太长了。 999,差不多5秒。

答案 1 :(得分:0)

更新:我知道结果应该在0到1000之间,但不是。对于较大的输入,天真的算法可能需要相当长的时间。 80的输出将是29999998880。


您不需要花哨的算法。在任何合理的现代计算机上,即使在解释型语言中,检查1000个数字条件的循环也只需不到2秒。

如果你想让它变得聪明,你只需要检查N倍数的数字。为了进一步限制搜索空间,N的余数和结果在除以9时必须相等。这意味着现在你必须每9N检查一个号码。

答案 2 :(得分:0)

当然,伪代码,因为它闻起来像家庭作业: - )

def findNum (n):
    testnum = n
    while testnum <= 1000:
        tempnum = testnum
        sum = 0
        while tempnum > 0:
            sum = sum + (tempnum mod 10)
            tempnum = int (tempnum / 10)
        if sum == n:
            return testnum
        testnum = testnum + n
    return -1

在你的两秒门槛下翻译成Python时需要大约15千分之一秒。它的工作原理是基本上测试N的每个倍数小于或等于1000。

测试遍历数字中的每个数字,然后将其添加到总和中,如果该总和与N匹配,则返回数字。如果编号符合条件,则返回-1。

作为测试用例,我使用了:

 n  findNum(n)     Justification
==  ==========     =============
 1           1       1 =  1 *  1,  1 = 1
10         190     190 = 19 * 10, 10 = 1 + 9 + 0
13         247     247 = 13 * 19, 13 = 2 + 4 + 7
17         476     476 = 17 * 28, 17 = 4 + 7 + 6
99          -1     none needed

现在只检查倍数高达1000而不是检查所有数字,但检查所有数字开始花费的时间超过两秒,无论您使用何种语言。您可能能够找到更快的算法,但我想提出其他建议。

您可能找不到比简单查找表中的值所需的算法更快的算法。所以,我只需运行一个程序一次来生成输出:

int numberDesired[] = {
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    190, 209, 48, 247, 266, 195, 448, 476, 198, 874,
    ...
    -1, -1};

然后将其插入新程序,以便它可以使用快速查找。

例如,您可以使用某些Python来执行此操作:

print "int numberDesired[] = {"
for i in range (0, 10):
    s = "    /* %4d-%4d */"%(i*10,i*10+9)
    for j in range (0, 10):
        s = "%s %d,"%(s,findNum(i*10+j))
    print s
print "};"

生成:

int numberDesired[] = {
    /*    0-   9 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    /*   10-  19 */ 190, 209, 48, 247, 266, 195, 448, 476, 198, 874,
    /*   20-  29 */ 3980, 399, 2398, 1679, 888, 4975, 1898, 999, 7588, 4988,
    /*   30-  39 */ 39990, 8959, 17888, 42999, 28798, 57995, 29988, 37999, 59888, 49998,
    /*   40-  49 */ 699880, 177899, 88998, 99889, 479996, 499995, 589996, 686999, 699888, 788998,
    /*   50-  59 */ 9999950, 889899, 1989988, 2989889, 1999998, 60989995, 7979888, 5899899, 8988898, 8888999,
    /*   60-  69 */ 79999980, 9998998, 19999898, 19899999, 59989888, 69999995, 67999998, 58999999, 99899888, 79899999,
:
};

这需要花费超过两秒的时间,但事情就是这样:你只需运行一次,然后将表格剪切并粘贴到代码中。一旦你有了表格,它很可能会破坏任何算法解决方案。

答案 3 :(得分:0)

您需要担心的最大数字总和是1000。由于1000 / 9 = ~100这实际上并不多,所以我认为以下内容应该有效:

考虑以下数据结构:

entry { int r, sum, prev, lastDigit; }

保留entry的队列,最初有r = 1 mod N, sum = 1, prev = -1, lastDigit = 1; r = 2 mod N, sum = 2, prev = -1, lastDigit = 2等。

从队列中提取条目x时:

y = new entry
for i = 0 to 9 do
  y.r = (x.r * 10 + i) % N
  y.sum = x.sum + i
  y.prev = <position of x in the queue>
  y.lastDigit = i

  if y.r == 0 and y.sum == N
    // you found your multiple: use the prev and lastDigit entries to rebuild it

  if y.sum < N then
    queue.add(y)

这基本上是数字上的BFS。由于你关心的最大金额很小,这应该非常有效。

答案 4 :(得分:0)

在考虑了一下之后,我想我找到了预期的答案。

将其视为图表。对于任何数字,您可以通过将该数字乘以10并添加任何数字0-9来创建新数字。您需要先使用BFS来达到最小的数字。

对于每个节点保持总和和余数。使用这些值可以移动到相邻节点,这些值也可以帮助您避免一次又一次地达到无用状态。要打印该数字,您可以使用这些值来跟踪您的步骤。 复杂度为O(n ^ 2),在最坏的情况下,表格完全填满。 (见代码)

注意:代码首先需要测试用例数。对于n <= 1000,工作在0.3s以下。

[编辑] :在6.54秒的时间内使用。测试文件有50个数字。

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define F first
#define S second 
#define N 1100
#define mp make_pair
queue<pair<int, int> >Q;
short sumTrace[N][N], mulTrace[N][N];

void print(int sum, int mul) {
    if (sumTrace[sum][mul] == 42)return;
    print(sum-sumTrace[sum][mul], mulTrace[sum][mul]);
    printf("%d",sumTrace[sum][mul]);
}
void solve(int n) {
    Q.push(mp(0,0));
    sumTrace[0][0]=42; // any number greater than 9
    while (1) {
        int sum = Q.front().F;
        int mul = Q.front().S;
        if (sum == n && mul == 0) break;
        Q.pop();
        for (int i=0; i<10; i++) {
            int nsum = sum+i;
            if (nsum > n)break;
            int nmul = (mul*10+i)%n;
            if (sumTrace[nsum][nmul] == -1) {
                Q.push(mp(nsum, nmul));
                sumTrace[nsum][nmul] = i;
                mulTrace[nsum][nmul] = mul;
            }
        }
    }
    print(n,0);
    while(!Q.empty())Q.pop();
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        memset(sumTrace, -1, sizeof sumTrace);
        solve(n);
        printf("\n");
    }
    return 0;
}